PSWinReporting.psm1

function Add-ServersToXML {
    param (
        [string] $FilePath,
        [string[]] $Servers
    )
    #$doc = New-Object System.Xml.XmlDocument
    #$doc.Load($filePath)
    [xml]$xmlDocument = Get-Content -Path $FilePath -Encoding UTF8

    foreach ($Server in $Servers) {
        $node = $xmlDocument.CreateElement('EventSource', $xmlDocument.Subscription.NamespaceURI)
        $node.SetAttribute('Enabled', 'true')

        $nodeServer = $xmlDocument.CreateElement('Address', $xmlDocument.Subscription.NamespaceURI)
        $nodeServer.set_InnerXML($Server)

        $xmlDocument.Subscription.Eventsources.AppendChild($node) > $null
        $xmlDocument.Subscription.Eventsources.EventSource.AppendChild($nodeServer) > $null
    }

    Save-XML -FilePath $FilePath -xml $xmlDocument
}
function Add-TaskScheduledForwarder {
    [CmdletBinding()]
    param(
        [string] $TaskPath = '\Event Viewer Tasks\',
        [string] $TaskName = 'ForwardedEvents',
        [string] $Author = 'Evotec',
        [string] $URI = '\Event Viewer Tasks\ForwardedEvents',
        [string] $Command = 'powershell.exe',
        [Array] $Argument = @('-windowstyle hidden', 'C:\Support\GitHub\PSWinReporting\Examples\Trigger.ps1', "-EventID $(eventID) -eventRecordID '$(eventRecordID)' -eventChannel '$(eventChannel)' -eventSeverity $(eventSeverity)"),
        [System.Collections.IDictionary] $LoggerParameters
    )
    if (-not $LoggerParameters) {
        $LoggerParameters = $Script:LoggerParameters
    }
    $Logger = Get-Logger @LoggerParameters
    $XmlTemplate = "$PSScriptRoot\Templates\Template-ScheduledTask.xml"
    if (Test-Path $xmlTemplate) {
        Write-Color 'Found Template ', $xmlTemplate -Color White, Yellow
        $ListTemplates = New-ArrayList
        if (Test-Path $xmlTemplate) {
            $ScheduledTaskXML = "$ENV:TEMP\PSWinReportingSchedluledTask.xml"
            Copy-Item -Path $xmlTemplate $ScheduledTaskXML
            Write-Color 'Copied template ', $ScheduledTaskXML -Color White, Yellow
            Set-XML -FilePath $ScheduledTaskXML -Paths 'Task', 'RegistrationInfo' -Node 'Author' -Value $Author
            Set-XML -FilePath $ScheduledTaskXML -Paths 'Task', 'Actions', 'Exec' -Node 'Command' -Value $Command
            Set-XML -FilePath $ScheduledTaskXML -Paths 'Task', 'Actions', 'Exec' -Node 'Arguments' -Value ([string] $Argument)

            $xml = (get-content $ScheduledTaskXML | out-string)
            try {
                $Output = Register-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName -xml $xml
            } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                switch ($ErrorMessage) {
                    default {
                        $Logger.AddErrorRecord("Tasks adding error occured: $ErrorMessage")
                    }
                }
                Exit
            }
            $Logger.AddInfoRecord("Loaded template $ScheduledTaskXML")
        }
    } else {
        $Logger.AddErrorRecord("Template not found $xmlTemplate")
    }
}
function Export-ReportToCSV {
    [CmdletBinding()]
    param (
        [bool] $Report,
        [System.Collections.IDictionary] $ReportOptions,
        [string] $Extension,
        [string] $ReportName,
        [Array] $ReportTable
    )
    if ($Report) {
        $ReportFilePath = Set-ReportFileName -ReportOptions $ReportOptions -ReportExtension $Extension -ReportName $ReportName
        if ($ReportTable.Count -gt 0) {
            $ReportTable | Export-Csv -Encoding Unicode -Path $ReportFilePath
        }
        return $ReportFilePath
    } else {
        return ''
    }
}
function Export-ReportToHTML {
    param (
        $Report,
        $ReportTable,
        $ReportTableText,
        [switch] $Special
    )
    if ($Report -eq $true) {
        if ($special) {
            return Set-EmailBodyPreparedTable -TableData $ReportTable -TableWelcomeMessage $ReportTableText
        }
        return Set-Emailbody -TableData $ReportTable -TableWelcomeMessage $ReportTableText
    } else {
        return ''
    }
}
function Export-ReportToSQL {
    [CmdletBinding()]
    param (
        [System.Collections.IDictionary] $Report,
        [System.Collections.IDictionary] $ReportOptions,
        [string] $ReportName,
        [Array] $ReportTable
    )
    if ($Report.Enabled) {
        # checks if Report is enabled
        if ($ReportOptions.Contains('AsSql') -and $ReportOptions.AsSql.Use) {
            # checks if global sql is enabled
            if ($Report.Contains('EnabledSqlGlobal') -and $Report.EnabledSqlGlobal) {
                # checks if global sql is enabled for particular dataset
                $Logger.AddInfoRecord("Sending $ReportName to SQL at Global level")
                $SqlQuery = Send-SqlInsert -Object $ReportTable -SqlSettings $ReportOptions.AsSql -Verbose:$ReportOptions.Debug.Verbose
                foreach ($Query in $SqlQuery) {
                    $Logger.AddInfoRecord("MS SQL Output: $Query")
                }
            }
        }
        if ($Report.Contains('ExportToSql') -and $Report.ExportToSql.Use) {
            # checks if local sql is enabled for dataset
            $Logger.AddInfoRecord("Sending $ReportName to SQL at Local level")
            $SqlQuery = Send-SqlInsert -Object $ReportTable -SqlSettings $Report.ExportToSql -Verbose:$ReportOptions.Debug.Verbose
            foreach ($Query in $SqlQuery) {
                $Logger.AddInfoRecord("MS SQL Output: $Query")
            }
        }
    }
}

function Export-ReportToXLSX {
    param(
        [bool] $Report,
        [System.Collections.IDictionary] $ReportOptions,
        [string] $ReportFilePath,
        [string] $ReportName,
        [Array] $ReportTable
    )
    if (($Report -eq $true) -and ($($ReportTable | Measure-Object).Count -gt 0)) {
        $ReportTable | ConvertTo-Excel -Path $ReportFilePath -WorkSheetname $ReportName -AutoSize -FreezeTopRow -AutoFilter
    }
}
function Export-ToCSV {
    [CmdletBinding()]
    param (
        [bool] $Report,
        [string] $Path,
        [string] $FilePattern,
        [string] $DateFormat,
        [string] $ReportName,
        [Array] $ReportTable
    )
    if ($Report) {
        $ReportFileName = Set-ReportFile -Path $Path -FileNamePattern $FilePattern -DateFormat $DateFormat -ReportName $ReportName
        try {
            if ($ReportTable.Count -gt 0) {
                $ReportTable | Export-Csv -Encoding Unicode -LiteralPath $ReportFileName -ErrorAction Stop -Force
            }
            return $ReportFileName
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            $Logger.AddErrorRecord("Error saving file $ReportFileName.")
            $Logger.AddErrorRecord("Error: $ErrorMessage")
            return ''
        }
    } else {
        return ''
    }
}
function Export-ToSQL {
    [CmdletBinding()]
    param (
        [System.Collections.IDictionary] $Report,
        [System.Collections.IDictionary] $ReportOptions,
        [string] $ReportName,
        [Array] $ReportTable
    )
    if ($Report.Enabled) {
        # checks if Report is enabled on global level and for that particular report
        if ($ReportOptions.Contains('AsSql') -and $ReportOptions.AsSql.Enabled -and $Report.Contains('SqlExport') -and $Report.SqlExport.EnabledGlobal) {
            # checks if global sql is enabled for particular dataset
            $Logger.AddInfoRecord("Sending $ReportName to SQL at Global level")
            $SqlQuery = Send-SqlInsert -Object $ReportTable -SqlSettings $ReportOptions.AsSql -Verbose:$ReportOptions.Debug.Verbose
            foreach ($Query in $SqlQuery) {
                $Logger.AddInfoRecord("MS SQL GLOBAL Output: $Query")
            }
        }
        if ($Report.Contains('SqlExport') -and $Report.SqlExport.Enabled) {
            # checks if local sql is enabled for dataset
            $Logger.AddInfoRecord("Sending $ReportName to SQL at Local level")
            $SqlQuery = Send-SqlInsert -Object $ReportTable -SqlSettings $Report.SqlExport -Verbose:$ReportOptions.Debug.Verbose
            foreach ($Query in $SqlQuery) {
                $Logger.AddInfoRecord("MS SQL LOCAL Output: $Query")
            }
        }
    }
}
function Find-AllEvents {
    param (
        [System.Collections.IDictionary] $ReportDefinitions,
        [string] $LogNameSearch,
        [switch] $All
    )
    $EventsToProcess = @()
    foreach ($report in $ReportDefinitions.ReportsAD.EventBased.GetEnumerator()) {
        $ReportName = $report.Name
        $Enabled = $ReportDefinitions.ReportsAD.EventBased.$ReportName.Enabled
        $LogName = $ReportDefinitions.ReportsAD.EventBased.$ReportName.LogName
        $Events = $ReportDefinitions.ReportsAD.EventBased.$ReportName.Events
        #$IgnoreWords = $ReportDefinitions.ReportsAD.EventBased.$ReportName.IgnoreWords

        if ($Enabled -eq $true -or $All -eq $true) {
            if ($LogNameSearch -eq $LogName) {
                $EventsToProcess += $Events
            }
        }
    }
    return $EventsToProcess
}
function Find-Events {
    [CmdLetBinding()]
    param(
        [parameter(ParameterSetName = "DateManual")][DateTime] $DateFrom,
        [parameter(ParameterSetName = "DateManual")][DateTime] $DateTo,
        [alias('Server', 'ComputerName')][string[]] $Servers = $Env:COMPUTERNAME,
        [alias('RunAgainstDC')][switch] $DetectDC,
        [switch] $Quiet,
        [System.Collections.IDictionary] $LoggerParameters
    )
    DynamicParam {
        # Defines Report / Dates Range dynamically from HashTables
        $Names = $Script:ReportDefinitions.Keys
        $ParamAttrib = New-Object System.Management.Automation.ParameterAttribute
        $ParamAttrib.Mandatory = $true
        $ParamAttrib.ParameterSetName = '__AllParameterSets'

        $ReportAttrib = New-Object  System.Collections.ObjectModel.Collection[System.Attribute]
        $ReportAttrib.Add($ParamAttrib)
        $ReportAttrib.Add((New-Object System.Management.Automation.ValidateSetAttribute($Names)))
        $ReportRuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Report', [string], $ReportAttrib)

        $DatesRange = $Script:ReportTimes.Keys
        $ParamAttribDatesRange = New-Object System.Management.Automation.ParameterAttribute
        $ParamAttribDatesRange.Mandatory = $true
        $ParamAttribDatesRange.ParameterSetName = 'DateRange'
        $DatesRangeAttrib = New-Object  System.Collections.ObjectModel.Collection[System.Attribute]
        $DatesRangeAttrib.Add($ParamAttribDatesRange)
        $DatesRangeAttrib.Add((New-Object System.Management.Automation.ValidateSetAttribute($DatesRange)))
        $DatesRangeRuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('DatesRange', [string], $DatesRangeAttrib)

        $RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $RuntimeParamDic.Add('Report', $ReportRuntimeParam)
        $RuntimeParamDic.Add('DatesRange', $DatesRangeRuntimeParam)
        return $RuntimeParamDic
    }

    Process {
        if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) { $Verbose = $true } else { $Verbose = $false }

        $Report = $PSBoundParameters.Report
        $DatesRange = $PSBoundParameters.DatesRange

        # Bring defaults
        $ReportTimes = $Script:ReportTimes
        $ReportDefinitions = $Script:ReportDefinitions

        ## Logging / Display to screen

        if (-not $LoggerParameters) {
            $LoggerParameters = $Script:LoggerParameters
        }
        $Logger = Get-Logger @LoggerParameters

        ##
        if ($DetectDC) {
            $ServersAD = Get-DC
            $Servers = ($ServersAD | Where-Object { $_.'Host Name' -ne 'N/A' }).'Host Name'
        }

        switch ($PSCmdlet.ParameterSetName) {
            DateRange {
                $ReportTimes.$DatesRange.Enabled = $true
            }
            DateManual {
                if ($DateFrom -and $DateTo) {
                    $ReportTimes.CustomDate.Enabled = $true
                    $ReportTimes.CustomDate.DateFrom = $DateFrom
                    $ReportTimes.CustomDate.DateTo = $DateTo
                } else {
                    return
                }
            }
        }
        [string] $ReportNameTitle = Format-AddSpaceToSentence -Text $Report
        if (-not $Quiet) { $Logger.AddInfoRecord("Running $ReportNameTitle report against $($Servers -join ', ')") }
        $Events = New-ArrayList
        $Dates = Get-ChoosenDates -ReportTimes $ReportTimes

        $MyReport = $ReportDefinitions[$Report]
        $LogNames = foreach ($SubReport in  $MyReport.Keys | Where-Object { $_ -ne 'Enabled' }) {
            $MyReport[$SubReport].LogName
        }
        $LogNames = $LogNames | Sort-Object -Unique


        foreach ($Log in $LogNames) {
            $EventsID = foreach ($R in $MyReport.Values) {
                if ($Log -eq $R.LogName) {
                    $R.Events
                    if (-not $Quiet) { $Logger.AddInfoRecord("Events scanning for Events ID: $($R.Events) ($Log)") }
                }
            }
            foreach ($Date in $Dates) {
                $ExecutionTime = Start-TimeLog
                if (-not $Quiet) { $Logger.AddInfoRecord("Getting events for dates $($Date.DateFrom) to $($Date.DateTo)") }
                $FoundEvents = Get-Events -Server $Servers `
                    -LogName $Log `
                    -EventID $EventsID `
                    -DateFrom $Date.DateFrom `
                    -DateTo $Date.DateTo `
                    -ErrorAction SilentlyContinue `
                    -ErrorVariable ErrorsReturned `
                    -Verbose:$Verbose

                foreach ($MyError in $ErrorsReturned) {
                    if (-not $Quiet) { $Logger.AddErrorRecord("Server $MyError") }
                }
                #$Logger.AddInfoRecord("Events: $EventsID Event Count: $($FoundEvents.Count)")
                Add-ToArrayAdvanced -List $Events -Element $FoundEvents -SkipNull -Merge
                $Elapsed = Stop-TimeLog -Time $ExecutionTime -Option OneLiner
                if (-not $Quiet) { $Logger.AddInfoRecord("Events scanned found $(Get-ObjectCount -Object $FoundEvents) - Time elapsed: $Elapsed") }
            }
        }
        return Get-MyEvents -Events $Events -ReportDefinition $MyReport -ReportName $Report -Quiet:$Quiet
    }
}
function Find-EventsNeeded {
    param (
        [Array] $Events,
        [alias('EventsNeeded')][Array] $EventIDs,
        [string] $EventsType = 'Security'
    )
    $EventsFound = foreach ($Event in $Events) {
        if ($Event.LogName -eq $EventsType) {
            if ($EventIDs -contains $Event.ID) {
                $Event
            }
        }
    }
    return $EventsFound
}
function Find-EventsTo {
    [CmdletBinding()]
    param (
        [Array] $Events,
        [alias('IgnoreWords', 'PriorityWords')] $DataSet,
        [switch] $Ignore,
        [switch] $Prioritize
    )
    if ((Get-ObjectCount -Object $DataSet) -eq 0) { return $Events }

    $EventsToReturn = foreach ($Event in $Events) {
        $Found = $false
        foreach ($Set in $DataSet.GetEnumerator()) {
            if ($Set.Value -ne '') {
                foreach ($Value in $Set.Value) {
                    $ColumnName = $Set.Name
                    if ($Event.$ColumnName -like $Value) {
                        $Found = $true
                    }
                }
            }
        }
        if ($Ignore) {
            if (-not $Found) {
                $Event
            }
        }
        if ($Prioritize) {
            if ($Found) {
                $Event
            }
        }
    }
    return $EventsToReturn
}
function Find-ServersAD {
    [CmdletBinding()]
    param (
        [Object] $DC,
        [Object] $ReportDefinitions
    )
    $Servers = @()

    if ($ReportDefinitions.ReportsAD.Servers.Automatic -eq $true) {
        # Cleans up empty HostName for failed domain
        $DC = $DC | Where-Object { $_.'Host Name' -ne 'N/A' }
        if ($ReportDefinitions.ReportsAD.Servers.OnlyPDC -eq $true) {
            $Servers += ($DC | Where-Object { $_.'Is PDC' -eq 'Yes' }).'Host Name'
        } else {
            $Servers += $DC.'Host Name'
        }
    } else {
        if ($ReportDefinitions.ReportsAD.Servers.DC -eq '' -and $ReportDefinitions.ReportsAD.Servers.UseForwarders -eq $false) {
            $Logger.AddErrorRecord("Parameter ReportDefinitions.ReportsAD.Servers.DC is empty. Please choose Automatic or fill in this field")
            exit
        } else {
            $Servers += $ReportDefinitions.ReportsAD.Servers.DC
        }
    }
    #}
    return $Servers
}
function Get-AllRequiredEvents {
    [CmdletBinding()]
    param(
        $Servers,
        [alias('File')][string] $FilePath,
        $Dates,
        $Events,
        [string] $LogName #,
        # [bool] $Verbose = $false
    )
    $Verbose = ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -eq $true)
    $Count = Get-ObjectCount $Events
    if ($Count -ne 0) {
        if ($FilePath) {
            $MyEvents = Get-Events -Path $FilePath -DateFrom $Dates.DateFrom -DateTo $Dates.DateTo `
                -EventID $Events `
                -LogName $LogName `
                -ErrorAction SilentlyContinue `
                -ErrorVariable ErrorsReturned `
                -Verbose:$Verbose

        } else {
            $MyEvents = Get-Events -Server $Servers -DateFrom $Dates.DateFrom -DateTo $Dates.DateTo -EventID $Events `
                -LogName $LogName `
                -ErrorAction SilentlyContinue `
                -ErrorVariable ErrorsReturned `
                -Verbose:$Verbose

        }

        foreach ($MyError in $ErrorsReturned) {
            Write-Error -Message $MyError
        }
        return $MyEvents
    }
}
function Get-ChoosenDates {
    param(
        [System.Collections.IDictionary] $ReportTimes
    )
    $Dates = @()

    # Report Per Hour
    if ($ReportTimes.Contains('PastHour') -and $ReportTimes.PastHour.Enabled) {
        $DatesPastHour = Find-DatesPastHour
        if ($DatesPastHour -ne $null) {
            $Dates += $DatesPastHour
        }
    }
    if ($ReportTimes.Contains('CurrentHour') -and $ReportTimes.CurrentHour.Enabled) {
        $DatesCurrentHour = Find-DatesCurrentHour
        if ($DatesCurrentHour -ne $null) {
            $Dates += $DatesCurrentHour
        }
    }
    # Report Per Day
    if ($ReportTimes.Contains('PastDay') -and $ReportTimes.PastDay.Enabled) {
        $DatesDayPrevious = Find-DatesDayPrevious
        if ($DatesDayPrevious -ne $null) {
            $Dates += $DatesDayPrevious
        }
    }
    if ($ReportTimes.Contains('CurrentDay') -and $ReportTimes.CurrentDay.Enabled) {
        $DatesDayToday = Find-DatesDayToday
        if ($DatesDayToday -ne $null) {
            $Dates += $DatesDayToday
        }
    }
    # Report Per Week
    if ($ReportTimes.Contains('OnDay') -and $ReportTimes.OnDay.Enabled) {
        foreach ($Day in $ReportTimes.OnDay.Days) {
            $DatesReportOnDay = Find-DatesPastWeek $Day
            if ($DatesReportOnDay -ne $null) {
                $Dates += $DatesReportOnDay
            }
        }
    }
    # Report Per Month
    if ($ReportTimes.Contains('PastMonth') -and $ReportTimes.PastMonth.Enabled) {
        # Find-DatesMonthPast runs only on 1st of the month unless -Force is used
        $DatesMonthPrevious = Find-DatesMonthPast -Force $ReportTimes.PastMonth.Force
        if ($DatesMonthPrevious -ne $null) {
            $Dates += $DatesMonthPrevious
        }
    }
    if ($ReportTimes.Contains('CurrentMonth') -and $ReportTimes.CurrentMonth.Enabled) {
        $DatesMonthCurrent = Find-DatesMonthCurrent
        if ($DatesMonthCurrent -ne $null) {
            $Dates += $DatesMonthCurrent
        }
    }
    # Report Per Quarter
    if ($ReportTimes.Contains('PastQuarter') -and $ReportTimes.PastQuarter.Enabled) {
        # Find-DatesMonthPast runs only on 1st of the quarter unless -Force is used
        $DatesQuarterLast = Find-DatesQuarterLast -Force $ReportTimes.PastQuarter.Force
        if ($DatesQuarterLast -ne $null) {
            $Dates += $DatesQuarterLast
        }
    }
    if ($ReportTimes.Contains('CurrentQuarter') -and $ReportTimes.CurrentQuarter.Enabled) {
        $DatesQuarterCurrent = Find-DatesQuarterCurrent
        if ($DatesQuarterCurrent -ne $null) {
            $Dates += $DatesQuarterCurrent
        }
    }
    # Report Custom
    if ($ReportTimes.Contains('CurrentDayMinusDayX') -and $ReportTimes.CurrentDayMinusDayX.Enabled) {
        $DatesCurrentDayMinusDayX = Find-DatesCurrentDayMinusDayX $ReportTimes.CurrentDayMinusDayX.Days
        if ($DatesCurrentDayMinusDayX -ne $null) {
            $Dates += $DatesCurrentDayMinusDayX
        }
    }
    if ($ReportTimes.Contains('CurrentDayMinuxDaysX') -and $ReportTimes.CurrentDayMinuxDaysX.Enabled) {
        $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX $ReportTimes.CurrentDayMinuxDaysX.Days
        if ($DatesCurrentDayMinusDaysX -ne $null) {
            $Dates += $DatesCurrentDayMinusDaysX
        }
    }
    if ($ReportTimes.Contains('CustomDate') -and $ReportTimes.CustomDate.Enabled) {
        $DatesCustom = @{
            DateFrom = $ReportTimes.CustomDate.DateFrom
            DateTo   = $ReportTimes.CustomDate.DateTo
        }
        if ($DatesCustom -ne $null) {
            $Dates += $DatesCustom
        }
    }
    if ($ReportTimes.Contains('Everything') -and $ReportTimes.Everything.Enabled) {
        $DatesEverything = @{
            DateFrom = Get-Date -Year 1600 -Month 1 -Day 1
            DateTo   = Get-Date -Year 2300 -Month 1 -Day 1
        }
        $Dates += $DatesEverything
    }
    if ($ReportTimes.Contains('Last3days') -and $ReportTimes.Last3days.Enabled) {
        $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 3
        if ($DatesCurrentDayMinusDaysX -ne $null) {
            $Dates += $DatesCurrentDayMinusDaysX
        }
    }
    if ($ReportTimes.Contains('Last7days') -and $ReportTimes.Last7days.Enabled) {
        $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 7
        if ($DatesCurrentDayMinusDaysX -ne $null) {
            $Dates += $DatesCurrentDayMinusDaysX
        }
    }
    if ($ReportTimes.Contains('Last14days') -and $ReportTimes.Last14days.Enabled) {
        $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 14
        if ($DatesCurrentDayMinusDaysX -ne $null) {
            $Dates += $DatesCurrentDayMinusDaysX
        }
    }
    return  $Dates
}
function Get-DC {
    [CmdletBinding()]
    param()
    try {
        $Forest = Get-ADForest
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        $Logger.AddErrorRecord("Get-ADForest Error: $ErrorMessage")
        exit
    }
    $DCs = @()
    foreach ($DomainName in $Forest.Domains) {
        try {
            $Domain = Get-AdDomain -Server $DomainName
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            switch ($ErrorMessage) {
                {$_ -match 'The server has rejected the client credentials'} {
                    $Logger.AddErrorRecord('Domain Controller has rejected the client credentials. Please run this script with access to Domain Controllers')
                }
                {$_ -match 'Unable to find a default server with Active Directory Web Services running' } {
                    $Logger.AddErrorRecord('Active Directory not found. Please run this script with access to Domain Controllers')
                }
                default {
                    $Logger.AddErrorRecord("Get-DC Error for domain $DomainName`: $ErrorMessage")
                }
            }

            $DCs += [PsCustomObject][ordered] @{
                'Name'             = 'Error'
                'Domain'           = $DomainName
                'Host Name'        = 'N/A'
                'Operating System' = 'N/A'
                'Site'             = 'N/A'
                'Ipv4'             = 'N/A'
                'Ipv6'             = 'N/A'
                'Is GC'            = 'No'
                'Is ReadOnly'      = 'No'
                'Is PDC'           = 'No'
                'Is Supported'     = 'No'
                'Is Included'      = 'No'
                'Enabled'          = 'No'
                'Comment'          = "$ErrorMessage"
            }
            continue
        }

        try {
            $DomainControllers = Get-ADDomainController -Server $DomainName -Filter *

            foreach ($Policy in $DomainControllers) {
                $DCs += [PsCustomObject][ordered] @{
                    'Name'             = $Policy.Name
                    'Domain'           = $DomainName
                    'Host Name'        = $Policy.HostName
                    'Operating System' = $Policy.OperatingSystem
                    'Site'             = $Policy.Site
                    'Ipv4'             = if ($Policy.Ipv4Address -eq '') { 'N/A' } else { $Policy.Ipv4Address }
                    'Ipv6'             = if ($Policy.Ipv6Address -eq '') { 'N/A' } else { $Policy.Ipv4Address }
                    'Is GC'            = if ($Policy.IsGlobalCatalog) { 'Yes' } else { 'No' }
                    'Is ReadOnly'      = if ($Policy.IsReadOnly) { 'Yes' } else { 'No' }
                    'Is PDC'           = if ($Policy.HostName -eq $Domain.PDCEmulator) { 'Yes' } else { 'No' }
                    'Is Supported'     = if ($Policy.OperatingSystem -notlike "*2003*" -and $Policy.OperatingSystem -notlike "*2000*") { 'Yes' } else { 'No' }
                    'Is Included'      = ''
                    'Enabled'          = if ($Policy.Enabled) { 'Yes' } else { 'No'}
                    'Comment'          = 'OK'
                }
            }
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            switch ($ErrorMessage) {
                {$_ -match 'The server has rejected the client credentials'} {
                    $Logger.AddErrorRecord('Domain Controller has rejected the client credentials. Please run this script with access to Domain Controllers')
                }
                {$_ -match 'Unable to find a default server with Active Directory Web Services running' } {
                    $Logger.AddErrorRecord('Active Directory not found. Please run this script with access to Domain Controllers')
                }
                default {
                    $Logger.AddErrorRecord("Get-DC Error for domain $DomainName`: $ErrorMessage")
                }
            }
            #exit
        }
    }
    return $DCs
}
function Get-EventLogFileList {
    [CmdletBinding()]
    param(
        $Sections
    )
    $EventFiles = New-ArrayList
    if ($Sections.Contains("Directories")) {
        foreach ($Folder in $Sections.Directories.Keys) {
            $Files = Get-FilesInFolder -Folder $Sections.Directories.$Folder -Extension '*.evtx'
            foreach ($File in $Files) {
                Add-ToArrayAdvanced -List $EventFiles -Element $File -RequireUnique
            }
        }
    }
    if ($Sections.Contains("Files")) {
        foreach ($FileName in $Sections.Files.Keys) {
            $File = $($Sections.Files.$FileName)
            Add-ToArrayAdvanced -List $EventFiles -Element $File -RequireUnique
        }
    }
    return $EventFiles
}
function Get-EventLogSize {
    [CmdletBinding()]
    param(
        [string[]] $Servers,
        [string] $LogName = "Security"
    )
    $Results = foreach ($Server in $Servers) {
        $result = @{
            Server  = $Server
            LogName = $LogName
        }
        try {
            $EventsInfo = Get-WinEvent -ListLog $LogName -ComputerName $Server
            $result.LogType = $EventsInfo.LogType
            $result.LogMode = $EventsInfo.LogMode
            $result.IsEnabled = $EventsInfo.IsEnabled
            $result.CurrentFileSize = Convert-Size -Value $EventsInfo.FileSize -From Bytes -To GB -Precision 2 -Display
            $result.MaximumFilesize = Convert-Size -Value $EventsInfo.MaximumSizeInBytes -From Bytes -To GB -Precision 2 -Display
            $result.EventOldest = (Get-WinEvent -MaxEvents 1 -LogName $LogName -Oldest -ComputerName $Server).TimeCreated
            $result.EventNewest = (Get-WinEvent -MaxEvents 1 -LogName $LogName -ComputerName $Server).TimeCreated
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            switch ($ErrorMessage) {
                {$_ -match 'No events were found'} {
                    $Logger.AddErrorRecord("$Server Reading Event Log ($LogName) size failed. No events found.")
                }
                {$_ -match 'Attempted to perform an unauthorized operation'} {
                    $Logger.AddErrorRecord("$Server Reading Event Log ($LogName) size failed. Unauthorized operation.")
                }
                default {
                    $Logger.AddErrorRecord("$Server Reading Event Log ($LogName) size failed. Error occured: $ErrorMessage")
                }
            }
        }
        [PSCustomObject]$result | Select-Object Server, LogName, LogType, EventOldest, EventNewest, "CurrentFileSize", "MaximumFileSize", LogMode, IsEnabled
    }
    return $Results
}
function Get-EventsData {
    [CmdletBinding()]
    param (
        [System.Collections.IDictionary] $ReportDefinitions,
        [string] $LogName
    )
    return Find-AllEvents -ReportDefinitions $ReportDefinitions -LogNameSearch $LogName | Sort-Object
}
function Get-EventsTranslation {
    [CmdletBinding()]
    param(
        [Array] $Events,
        [System.Collections.IDictionary] $EventsDefinition  # HashTable/OrderedDictionary
    )
    if ($EventsDefinition.Filter) {
        # Filter is special, if there is just one object on the right side
        # If there are more objects filter will pick all values on the right side and display them as required
        #Filter = @{
        # 'ObjectClass' = 'groupPolicyContainer'
        # 'AttributeLDAPDisplayName' = 'cn','displayName'
        #}

        foreach ($Filter in $EventsDefinition.Filter.Keys) {
            $Value = $EventsDefinition.Filter[$Filter]
            $Events = foreach ($V in $Value) {
                $Events | Where-Object { $_.$Filter -eq $V }
            }
        }
    }
    $MyValue = foreach ($Event in $Events) {
        $IgnoredFound = $false
        $HashTable = @{}
        foreach ($EventProperty in $Event.PSObject.Properties) {

            # $EventProperty.Name is value on the left side
            # $Fields[$EventProperty.Name] is value on the right side
            # $EventProperty.Value is actual value of field

            # Check if defined field invalidates whole Event
            if ($null -ne $EventsDefinition.Ignore) {
                if ($EventsDefinition.Ignore.Contains($EventProperty.Name)) {
                    if ($EventProperty.Value -like $EventsDefinition.Ignore[$EventProperty.Name]) {
                        continue
                    }
                }
            }
            # Check if field requires functions
            if ($null -ne $EventsDefinition.Functions) {
                if ($EventsDefinition.Functions.Contains($EventProperty.Name)) {
                    if ($EventsDefinition.Functions[$EventProperty.Name] -contains 'Remove-WhiteSpace') {
                        $EventProperty.Value = Remove-WhiteSpace -Text $EventProperty.Value
                    }
                    if ($EventsDefinition.Functions[$EventProperty.Name] -contains 'Split-OnSpace') {
                        $EventProperty.Value = $EventProperty.Value -Split ' '
                    }
                    if ($EventsDefinition.Functions[$EventProperty.Name] -contains 'Convert-UAC') {
                        $EventProperty.Value = Convert-UAC -UAC $EventProperty.Value -Separator ', '
                    }
                    if ($EventsDefinition.Functions[$EventProperty.Name] -contains 'ConvertFrom-OperationType') {
                        $EventProperty.Value = ConvertFrom-OperationType -OperationType $EventProperty.Value
                    }
                    if ($EventsDefinition.Functions[$EventProperty.Name] -contains 'Clean-IpAddress') {
                        $EventProperty.Value = if ($EventProperty.Value -match "::1") { 'localhost' } else { $EventProperty.Value }
                    }
                }
            }

            if ($null -ne $EventsDefinition.Fields -and $EventsDefinition.Fields.Contains($EventProperty.Name)) {
                # Replaces Field with new Field according to schema
                $HashTable[$EventsDefinition.Fields[$EventProperty.Name]] = $EventProperty.Value
            } else {
                # This is your standard event field, we won't be using it for display most of the time
                # May need to be totally ignored if not needed. To be decided
                # Assign value - Finally (untouched one)
                $HashTable[$EventProperty.Name] = $EventProperty.Value
            }
        }
        #$HashTable | fs

        # This overwrites values based on parameters. It's useful for cleanup or special cases.
        if ($null -ne $EventsDefinition.Overwrite) {
            foreach ($Entry in $EventsDefinition.Overwrite.Keys) {
                $OverwriteObject = $EventsDefinition.Overwrite.$Entry
                # This allows for having multiple values in Overwrite by using additional empty spaces in definition
                $StrippedEntry = Remove-WhiteSpace -Text $Entry
                if ($OverwriteObject.Count -eq 3) {
                    #Write-Color $Entry, ' - ', $HashTable[$Entry], ' - ', $HashTable.$Entry, ' - ', $HashTable.($OverwriteObject[0]) -Color Red
                    #Write-Color $OverwriteObject[0], ' - ', $OverwriteObject[1], ' - ', $OverwriteObject[2] -Color Yellow
                    if ($HashTable.($OverwriteObject[0]) -eq $OverwriteObject[1]) {
                        $HashTable.$StrippedEntry = $OverwriteObject[2]
                    }
                } elseif ($OverwriteObject.Count -eq 4) {
                    if ($HashTable.($OverwriteObject[0]) -eq $OverwriteObject[1]) {
                        $HashTable.$StrippedEntry = $OverwriteObject[2]
                    } else {
                        $HashTable.$StrippedEntry = $OverwriteObject[3]
                    }
                }
            }
        }
        [PsCustomObject]$HashTable

    }
    $MyValue = Find-EventsTo -Ignore -Events $MyValue -DataSet $EventsDefinition.IgnoreWords

    if ($null -eq $EventsDefinition.Fields) {
        return $MyValue | Sort-Object $EventsDefinition.SortBy
    } else {
        return $MyValue | Select-Object @($EventsDefinition.Fields.Values) | Sort-Object $EventsDefinition.SortBy
    }
}
function Get-EventsWorkaround {
    [CmdLetBinding()]
    param(
        [Array] $Events,
        [System.Collections.IDictionary] $IgnoreWords
    )
    DynamicParam {
        # Defines Report / Dates Range dynamically from HashTables
        $Names = $Script:ReportDefinitions.Keys

        $ParamAttrib = New-Object System.Management.Automation.ParameterAttribute
        $ParamAttrib.Mandatory = $true
        $ParamAttrib.ParameterSetName = '__AllParameterSets'

        $ReportAttrib = New-Object  System.Collections.ObjectModel.Collection[System.Attribute]
        $ReportAttrib.Add($ParamAttrib)
        $ReportAttrib.Add((New-Object System.Management.Automation.ValidateSetAttribute($Names)))

        $ReportRuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Report', [string], $ReportAttrib)

        $RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $RuntimeParamDic.Add('Report', $ReportRuntimeParam)
        return $RuntimeParamDic
    }
    Process {
        [string] $ReportName = $PSBoundParameters.Report
        $MyReportDefinitions = $Script:ReportDefinitions.$ReportName

        foreach ($Report in $MyReportDefinitions.Keys | Where-Object { $_ -ne 'Enabled' }) {

            $MyReportDefinitions.$Report.IgnoreWords = $IgnoreWords
            $EventsType = $MyReportDefinitions[$Report].LogName
            $EventsNeeded = $MyReportDefinitions[$Report].Events
            $EventsFound = Find-EventsNeeded -Events $Events -EventsNeeded $EventsNeeded -EventsType $EventsType
            $EventsFound = Get-EventsTranslation -Events $EventsFound -EventsDefinition $MyReportDefinitions[$Report]
            $Logger.AddInfoRecord("Events Processed: $($EventsFound.Count), EventsType: $EventsType EventsID: $EventsNeeded")
            $EventsFound
        }
    }
}
function Get-MyEvents {
    [CmdLetBinding()]
    param(
        [Array] $Events,
        [System.Collections.IDictionary] $ReportDefinition,
        [switch] $Quiet
    )
    DynamicParam {
        # Defines Report / Dates Range dynamically from HashTables
        $Names = $Script:ReportDefinitions.Keys

        $ParamAttrib = New-Object System.Management.Automation.ParameterAttribute
        $ParamAttrib.Mandatory = $true
        $ParamAttrib.ParameterSetName = '__AllParameterSets'

        $ReportAttrib = New-Object  System.Collections.ObjectModel.Collection[System.Attribute]
        $ReportAttrib.Add($ParamAttrib)
        $ReportAttrib.Add((New-Object System.Management.Automation.ValidateSetAttribute($Names)))

        $ReportRuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('ReportName', [string], $ReportAttrib)

        $RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $RuntimeParamDic.Add('ReportName', $ReportRuntimeParam)
        return $RuntimeParamDic
    }
    Process {
        [string] $ReportName = $PSBoundParameters.ReportName
        [string] $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName
        if (-not $Quiet) { $Logger.AddInfoRecord("Running beautification process, applying filters for $ReportNameTitle report.") }
        $ExecutionTime = Start-TimeLog

        foreach ($Report in $ReportDefinition.Keys | Where-Object { $_ -ne 'Enabled' }) {
            $EventsType = $ReportDefinition[$Report].LogName
            $EventsNeeded = $ReportDefinition[$Report].Events
            $EventsFound = Find-EventsNeeded -Events $Events -EventsNeeded $EventsNeeded -EventsType $EventsType
            $EventsFound = Get-EventsTranslation -Events $EventsFound -EventsDefinition $ReportDefinition[$Report]
            $EventsFound
        }

        $EventsRemoved = $Events.Count - $EventsFound.Count
        $Elapsed = Stop-TimeLog -Time $ExecutionTime -Option OneLiner
        if (-not $Quiet) { $Logger.AddInfoRecord("Events returned: $($EventsFound.Count), events skipped: $EventsRemoved") }
        if (-not $Quiet) { $Logger.AddInfoRecord("Ending beautification process, applying filters for $ReportNameTitle report - Time elapsed: $Elapsed") }

    }
}
function Get-NotificationParameters {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Notifications,
        [string] $ActivityTitle,
        [string] $Priority,
        [string] $Type
    )
    $Object = @{
        Uri               = ''
        ActivityImageLink = ''
        Color             = ''
        AvatarImage       = ''
        AvatarName        = ''
    }

    if ($null -ne $Notifications.$Priority) {
        $Logger.AddInfoRecord("Service $Type is using $Priority priority Event on $ActivityTitle")
        $Option = $Priority
    } else {
        $Logger.AddInfoRecord("Service $Type is using Default priority Event on $ActivityTitle")
        $Option = 'Default'

    }
    $Object.Uri = $Notifications[$Option].Uri
    ## Only for discord
    $Object.AvatarName = $Notifications[$Option].AvatarName
    $Object.AvatarImage = $Notifications[$Option].AvatarImage

    # All Slack/Discord/Teams
    $Object.ActivityImageLink = $Notifications[$Option].ActivityLinks.Default.Link
    $Object.Color = $Notifications[$Option].ActivityLinks.Default.Color
    foreach ($Type in $Notifications[$option].ActivityLinks.Keys | Where-Object { $_ -ne 'Default' }) {
        if ($ActivityTitle -like "*$Type*") {
            $Object.ActivityImageLink = $Notifications[$Option].ActivityLinks.$Type.Link
            $Object.Color = $Notifications[$Option].ActivityLinks.$Type.Color
            break
        }
    }
    return $Object
}
function Get-ServersPermissions {
    param (
        [string] $ProgramWevtutil,
        [string[]] $Servers,
        [string]$LogName = 'security'
    )


    foreach ($DC in $Servers) {
        $cmdArgListGet = @(
            "gl"
            $LogName
            "/r:$DC"
        )
        Start-MyProgram -Program $Script:ProgramWevtutil -cmdArgList $cmdArgListGet
    }
}
function Get-ServersList {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Definitions,
        [System.Collections.IDictionary] $Target
    )
    # Get Servers
    $ServersList = New-ArrayList
    if ($Target.Servers.Enabled) {
        $Logger.AddInfoRecord("Preparing servers list - defined list")
        [Array] $Servers = foreach ($Server in $Target.Servers.Keys | Where-Object { $_ -ne 'Enabled' }) {

            if ($Target.Servers.$Server -is [System.Collections.IDictionary]) {
                [ordered] @{
                    ComputerName = $Target.Servers.$Server.ComputerName
                    LogName      = $Target.Servers.$Server.LogName
                }

            } elseif ($Target.Servers.$Server -is [Array] -or $Target.Servers.$Server -is [string]) {
                $Target.Servers.$Server
            }
        }
        $null = $ServersList.AddRange($Servers)
    }
    if ($Target.DomainControllers.Enabled) {
        $Logger.AddInfoRecord("Preparing servers list - domain controllers autodetection")
        [Array] $Servers = (Get-WinADDomainControllers -SkipEmpty).HostName
        $null = $ServersList.AddRange($Servers)
    }
    if ($Target.LocalFiles.Enabled) {
        $Logger.AddInfoRecord("Preparing file list - defined event logs")
        $Files = Get-EventLogFileList -Sections $Target.LocalFiles
    }
    # Prepare list of servers and files to scan and their relation to LogName and EventIDs and DataTimes
    <#
        Server LogName EventID Type
        ------ ------- ------- ----
        AD1 Security {5136, 5137, 5141, 5136...} Computer
        AD2 Security {5136, 5137, 5141, 5136...} Computer
        EVO1 ForwardedEvents {5136, 5137, 5141, 5136...} Computer
        AD1.ad.evotec.xyz Security {5136, 5137, 5141, 5136...} Computer
        AD2.ad.evotec.xyz Security {5136, 5137, 5141, 5136...} Computer
        C:\MyEvents\Archive-Security-2018-08-21-23-49-19-424.evtx Security {5136, 5137, 5141, 5136...} File
        C:\MyEvents\Archive-Security-2018-09-08-02-53-53-711.evtx Security {5136, 5137, 5141, 5136...} File
        C:\MyEvents\Archive-Security-2018-09-14-22-13-07-710.evtx Security {5136, 5137, 5141, 5136...} File
        C:\MyEvents\Archive-Security-2018-09-15-09-27-52-679.evtx Security {5136, 5137, 5141, 5136...} File
        AD1 System 104 Computer
        AD2 System 104 Computer
        EVO1 ForwardedEvents 104 Computer
        AD1.ad.evotec.xyz System 104 Computer
        AD2.ad.evotec.xyz System 104 Computer
        C:\MyEvents\Archive-Security-2018-08-21-23-49-19-424.evtx System 104 File
        C:\MyEvents\Archive-Security-2018-09-08-02-53-53-711.evtx System 104 File
        C:\MyEvents\Archive-Security-2018-09-14-22-13-07-710.evtx System 104 File
        C:\MyEvents\Archive-Security-2018-09-15-09-27-52-679.evtx System 104 File
    #>


    # Get LogNames
    [Array] $LogNames = foreach ($Report in  $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport'}) {
        if ($Definitions.$Report.Enabled) {
            foreach ($SubReport in $Definitions.$Report.Keys | Where-Object { $_ -notcontains 'Enabled' }) {
                if ($Definitions.$Report.$SubReport.Enabled) {
                    $Definitions.$Report.$SubReport.LogName
                }
            }
        }
    }
    [Array] $ExtendedInput = foreach ($Log in $LogNames | Sort-Object -Unique) {
        $EventIDs = foreach ($Report in  $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport'}) {
            if ($Definitions.$Report.Enabled) {
                foreach ($SubReport in $Definitions.$Report.Keys | Where-Object { $_ -notcontains 'Enabled' }) {
                    if ($Definitions.$Report.$SubReport.Enabled) {
                        if ($Definitions.$Report.$SubReport.LogName -eq $Log) {
                            $Definitions.$Report.$SubReport.Events
                        }
                    }
                }
            }
        }
        #$Logger.AddInfoRecord("Preparing to scan log $Log for Events:$($EventIDs -join ', ')")

        $OutputServers = foreach ($Server in $ServersList) {
            if ($Server -is [System.Collections.IDictionary]) {
                [PSCustomObject]@{
                    Server   = $Server.ComputerName
                    LogName  = $Server.LogName
                    EventID  = $EventIDs | Sort-Object -Unique
                    Type     = 'Computer'
                    DateFrom = $Dates.DateFrom
                    DateTo   = $Dates.DateTo
                }
            } elseif ($Server -is [Array] -or $Server -is [string]) {
                foreach ($S in $Server) {
                    [PSCustomObject]@{
                        Server   = $S
                        LogName  = $Log
                        EventID  = $EventIDs | Sort-Object -Unique
                        Type     = 'Computer'
                        DateFrom = $Dates.DateFrom
                        DateTo   = $Dates.DateTo
                    }
                }
            }
        }
        $OutputFiles = foreach ($File in $FIles) {
            [PSCustomObject]@{
                Server   = $File
                LogName  = $Log
                EventID  = $EventIDs | Sort-Object -Unique
                Type     = 'File'
                DateFrom = $Dates.DateFrom
                DateTo   = $Dates.DateTo
            }
        }
        $OutputServers
        $OutputFiles
    }
    , $ExtendedInput
}
function Get-ServersListLimited {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Target,
        [int64] $RecordID
    )
    $ServersList = New-ArrayList
    if ($Target.Servers.Enabled) {
        $Logger.AddInfoRecord("Preparing servers list - defined list")
        [Array] $Servers = foreach ($Server in $Target.Servers.Keys | Where-Object { $_ -ne 'Enabled' }) {

            if ($Target.Servers.$Server -is [System.Collections.IDictionary]) {
                [ordered] @{
                    ComputerName = $Target.Servers.$Server.ComputerName
                    LogName      = $Target.Servers.$Server.LogName
                }

            } elseif ($Target.Servers.$Server -is [Array] -or $Target.Servers.$Server -is [string]) {
                $Target.Servers.$Server
            }
        }
        $null = $ServersList.AddRange($Servers)
    }
    [Array] $ExtendedInput = foreach ($Server in $ServersList) {
        [PSCustomObject] @{
            Server   = $Server.ComputerName
            LogName  = $Server.LogName
            RecordID = $RecordID
        }
    }
    , $ExtendedInput
}
function Invoke-EventLogVerification {
    [CmdletBinding()]
    param(
        $Results,
        $Dates
    )
    $Logs = @()
    foreach ($result in $Results) {

        if ($result.EventOldest -gt $Dates.DateFrom) {
            $Logger.AddWarningRecord("$($Result.Server)`: $($Result.LogName) log on doesn't cover whole date range requested. Oldest event $($Result.EventOldest) while requested $($Dates.DateFrom).")
           # Write-Color @script:WriteParameters '[W] ', 'Warning: ', $result.LogName, ' log on ', $result.Server, " doesn't cover whole date range requested. Oldest event ", $result.EventOldest, ' while requested ', $Dates.DateFrom, '.' -Color Blue, White, Yellow, White, Yellow, White, Yellow, White, Yellow
            $Logs += "<strong>$($result.Server)</strong>`: <strong>$($result.LogName)</strong> log on doesn't cover whole date range requested. Oldest event <strong>$($result.EventOldest)</strong> while requested <strong>$($Dates.DateFrom)</strong>."
        }
    }
    return $Logs
}
function Move-ArchivedLogs {
    [CmdletBinding()]
    param (
        [string] $ServerName,
        [string] $SourcePath,
        [string] $DestinationPath
    )
    $NewSourcePath = "\\$ServerName\$($SourcePath.Replace(':\','$\'))"
    $PathExists = Test-Path $NewSourcePath
    if ($PathExists) {
        Write-Color @script:WriteParameters '[i] Moving log file from ', $NewSourcePath, ' to ', $DestinationPath -Color White, Yellow, White, Yellow
        Move-Item -Path $NewSourcePath -Destination $DestinationPath
        if (!$?) {
            Write-Color @script:WriteParameters '[i] File ', $NewSourcePath, ' couldn not be moved.' -Color White, Yellow, White
        }
    } else {
        Write-Color @script:WriteParameters '[i] Event Log Move ', $NewSourcePath, ' was skipped. No file exists on drive.' -Color White, Yellow, White, Yellow
    }
}

function New-EventQuery {
    [CmdletBinding()]
    param (
        [string[]]$Events,
        [string] $Type
    )
    <#
        <![CDATA[
        <QueryList>
        <Query Id="0" Path="Security">
        <Select Path="Security">*[System[(EventID=122 or EventID=212 or EventID=323)]]</Select>
        </Query>
        </QueryList>
                ]]>
    #>

    Write-Verbose "New-EventQuery - Events Count: $($Events.Count)"
    $values = New-ArrayList
    # Add-ToArray -List $Values -Element '<![CDATA[ <QueryList><Query Id="0" Path="Security">'
    Add-ToArray -List $Values -Element '<QueryList><Query Id="0" Path="Security">'
    Add-ToArray -List $Values -Element "<Select Path=`"$Type`">*[System[("
    foreach ($E in $Events) {
        Add-ToArray -List $Values -Element "EventID=$E"
        Add-ToArray -List $Values -Element "or"
    }
    Remove-FromArray -List $values -LastElement
    #Add-ToArray -List $Values -Element ')]]</Select></Query></QueryList>]]>'
    Add-ToArray -List $Values -Element ')]]</Select></Query></QueryList>'
    $FinalQuery = ([string] $Values)
    Write-Verbose $FinalQuery
    return ([string] $Values) #.Replace(' ', '').Replace('or', ' or ').Replace('SelectPath', 'Select Path')
}
function New-SubscriptionTemplates {
    [CmdletBinding()]
    param (
        [alias('ReportDefinitions')][System.Collections.IDictionary] $Definitions,
        [alias('Servers', 'Computers')][System.Collections.IDictionary] $Target,
        [System.Collections.IDictionary] $LoggerParameters
    )
    if (-not $LoggerParameters) {
        $LoggerParameters = $Script:LoggerParameters
    }
    $Logger = Get-Logger @LoggerParameters


    [Array] $ExtendedInput = Get-ServersList -Definitions $Definitions -Target $Target
    foreach ($Entry in $ExtendedInput) {
        if ($Entry.Type -eq 'Computer') {
            $Logger.AddInfoRecord("Computer $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')")
        } else {
            $Logger.AddInfoRecord("File $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')")
        }
    }

    return $ExtendedInput

    return



    $Events = Get-EventsData -ReportDefinitions $Definitions -LogName 'Security'
    $Systems = Get-EventsData -ReportDefinitions $Definitions -LogName 'System'

    $Logger.AddInfoRecord("Found Security Events $($Events -join ', ')")
    $Logger.AddInfoRecord("Found System Events $($Systems -join ', ')")

    $ServersAD = Get-DC
    $Servers = Find-ServersAD -ReportDefinitions $Definitions -DC $ServersAD
    #Write-Color 'Found Servers ', ([string] $Servers) -Color White, Yellow
    $Logger.AddInfoRecord("Found Servers $($Servers -join ', ')")
    # $xmlTemplate = "$($($(Get-Module -ListAvailable PSWinReporting)[0]).ModuleBase)\Templates\Template-Collector.xml"
    $XmlTemplate = "$((get-item $PSScriptRoot).Parent.FullName)\Templates\Template-Collector.xml"
    if (Test-Path $xmlTemplate) {
        $Logger.AddInfoRecord("Found Template $xmlTemplate")
        #Write-Color 'Found Template ', $xmlTemplate -Color White, Yellow
        $ListTemplates = New-ArrayList
        if (Test-Path $xmlTemplate) {
            $Array = New-ArrayList
            $SplitArrayID = Split-Array -inArray $Events -size 22  # Support for more ID's then 22 (limitation of Get-WinEvent)
            foreach ($ID in $SplitArrayID) {
                $Query = New-EventQuery -Events $ID -Type 'Security' #-Verbose
                Add-ToArray -List $Array -Element $Query
            }
            $SplitArrayID = Split-Array -inArray $Systems -size 22  # Support for more ID's then 22 (limitation of Get-WinEvent)
            foreach ($ID in $SplitArrayID) {
                $Query = New-EventQuery -Events $ID -Type 'System' #-Verbose
                Add-ToArray -List $Array -Element $Query
            }
            $i = 0
            foreach ($Events in $Array) {
                $i++
                $SubscriptionTemplate = "$ENV:TEMP\PSWinReportingSubscription$i.xml"
                Copy-Item -Path $xmlTemplate $SubscriptionTemplate
                $Logger.AddInfoRecord("Copied template $SubscriptionTemplate")
                #Write-Color 'Copied template ', $SubscriptionTemplate -Color White, Yellow
                Add-ServersToXML -FilePath $SubscriptionTemplate -Servers $Servers

                Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'SubscriptionId' -Value "PSWinReporting Subscription Events - $i"
                Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'ContentFormat' -Value 'Events'
                Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'ConfigurationMode' -Value 'Custom'
                #$Events
                Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'Query' -Value $Events
                Add-ToArray -List $ListTemplates -Element $SubscriptionTemplate
            }

        }
    } else {
        $Logger.AddInfoRecord("Template not found $xmlTemplate")
        #Write-Color 'Template not found ', $xmlTemplate -Color White, Yellow
    }
    return $ListTemplates
}

function Protect-ArchivedLogs {
    [CmdletBinding()]
    param (
        $TableEventLogClearedLogs,
        [string] $DestinationPath
    )

    <#
        $EventsFound = $EventsFound | Select-Object @{label = 'Domain Controller'; expression = { $_.Computer}} ,
        @{label = 'Action'; expression = { ($_.Message -split '\n')[0] }},
        @{label = 'Backup Path'; expression = { if ($_.BackupPath -eq $null) { 'N/A' } else { $_.BackupPath} }},
        @{label = 'Log Type'; expression = { if ($Type -eq 'Security') { 'Security' } else { $_.Channel } }},
        @{label = 'Who'; expression = { if ($_.ID -eq 1105) { "Automatic Backup" } else { "$($_.SubjectDomainName)\$($_.SubjectUserName)" }}},
        @{label = 'When'; expression = { $_.Date }},
        @{label = 'Event ID'; expression = { $_.ID }},
        @{label = 'Record ID'; expression = { $_.RecordId }} | Sort-Object When
        $EventsFound = Find-EventsIgnored -Events $EventsFound -IgnoreWords $IgnoreWords
    #>

    foreach ($BackupEvent in $TableEventLogClearedLogs) {
        if ($BackupEvent.'Event ID' -eq 1105) {
            $SourcePath = $BackupEvent.'Backup Path'
            $ServerName = $BackupEvent.'Domain Controller'
            if ($SourcePath -and $ServerName -and $DestinationPath) {
                Write-Color @script:WriteParameters '[i] Found Event Log file ', $SourcePath, ' on ', $ServerName, '. Will try moving to: ', $DestinationPath -Color White, Yellow, White, Yellow
                Move-ArchivedLogs -ServerName $ServerName -SourcePath $SourcePath -DestinationPath $DestinationPath
            }
        }
    }
}
function Remove-ReportsFiles {
    [CmdletBinding()]
    param(
        [bool] $KeepReports,
        [Array] $ReportFiles
    )
    if (-not $KeepReports) {
        foreach ($Report in $ReportFiles) {
            if ($Report -ne '' -and (Test-Path -LiteralPath $Report)) {
                $Logger.AddInfoRecord("Removing file $Report")
                try {
                    Remove-Item -LiteralPath $Report -ErrorAction Stop
                } catch {
                    $Logger.AddErrorRecord("Error removing file: $($_.Exception.Message)")
                }
            }
        }
    }
}
function Remove-Subscription {
    [CmdletBinding()]
    param(
        [switch] $All,
        [switch] $Own
    )
    $Subscriptions = Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'es'
    foreach ($Subscription in $Subscriptions) {
        if ($Own -eq $true -and $Subscription -like '*PSWinReporting*') {
            $Logger.AddInfoRecord("Deleting own providers - $Subscription")
            #Write-Color 'Deleting own providers - ', $Subscription -Color White, Green
            Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'ds', $Subscription
        }
        if ($All -eq $true -and $Subscription -notlike '*PSWinReporting*') {
            $Logger.AddInfoRecord("Deleting own providers - $Subscription")
            #Write-Color 'Deleting all providers - ', $Subscription -Color White, Green
            Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'ds', $Subscription
        }

    }
}
function Remove-TaskScheduledForwarder {
    [CmdletBinding()]
    param(
        [string] $TaskPath = '\Event Viewer Tasks\',
        [string] $TaskName = 'ForwardedEvents',
        [System.Collections.IDictionary] $LoggerParameters
    )
    if (-not $LoggerParameters) {
        $LoggerParameters = $Script:LoggerParameters
    }
    $Logger = Get-Logger @LoggerParameters
    try {
        Unregister-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName -Confirm:$false -ErrorAction Stop
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        switch ($ErrorMessage) {
            {$_ -match 'No matching MSFT_ScheduledTask objects found by CIM query for instances of the'} {
                $Logger.AddInfoRecord("No tasks exists. Nothing to remove")
            }
            default {
                $Logger.AddErrorRecord("Tasks removal error: $ErrorMessage")
            }
        }
    }
}
$Script:LoggerParameters = @{
    ShowTime   = $false
    TimeFormat = 'yyyy-MM-dd HH:mm:ss'
}
$Script:ProgramWecutil = "wecutil.exe"
$Script:ProgramWevtutil = 'wevtutil.exe'
## Define reports
$Script:ReportDefinitions = [ordered] @{
    UserChanges             = @{
        Enabled   = $true
        SqlExport = @{
            EnabledGlobal         = $false
            Enabled               = $false
            SqlServer             = 'EVO1'
            SqlDatabase           = 'SSAE18'
            SqlTable              = 'dbo.[EventsNewSpecial]'
            # Left side is data in PSWinReporting. Right Side is ColumnName in SQL
            # Changing makes sense only for right side...
            SqlTableCreate        = $true
            SqlTableAlterIfNeeded = $false # if table mapping is defined doesn't do anything
            SqlCheckBeforeInsert  = 'EventRecordID', 'DomainController' # Based on column name

            SqlTableMapping       = [ordered] @{
                'Event ID'               = 'EventID,[int]'
                'Who'                    = 'EventWho'
                'When'                   = 'EventWhen,[datetime]'
                'Record ID'              = 'EventRecordID,[bigint]'
                'Domain Controller'      = 'DomainController'
                'Action'                 = 'Action'
                'Group Name'             = 'GroupName'
                'User Affected'          = 'UserAffected'
                'Member Name'            = 'MemberName'
                'Computer Lockout On'    = 'ComputerLockoutOn'
                'Reported By'            = 'ReportedBy'
                'SamAccountName'         = 'SamAccountName'
                'Display Name'           = 'DisplayName'
                'UserPrincipalName'      = 'UserPrincipalName'
                'Home Directory'         = 'HomeDirectory'
                'Home Path'              = 'HomePath'
                'Script Path'            = 'ScriptPath'
                'Profile Path'           = 'ProfilePath'
                'User Workstation'       = 'UserWorkstation'
                'Password Last Set'      = 'PasswordLastSet'
                'Account Expires'        = 'AccountExpires'
                'Primary Group Id'       = 'PrimaryGroupId'
                'Allowed To Delegate To' = 'AllowedToDelegateTo'
                'Old Uac Value'          = 'OldUacValue'
                'New Uac Value'          = 'NewUacValue'
                'User Account Control'   = 'UserAccountControl'
                'User Parameters'        = 'UserParameters'
                'Sid History'            = 'SidHistory'
                'Logon Hours'            = 'LogonHours'
                'OperationType'          = 'OperationType'
                'Message'                = 'Message'
                'Backup Path'            = 'BackupPath'
                'Log Type'               = 'LogType'
                'AddedWhen'              = 'EventAdded,[datetime],null' # ColumnsToTrack when it was added to database and by who / not part of event
                'AddedWho'               = 'EventAddedWho'  # ColumnsToTrack when it was added to database and by who / not part of event
                'Gathered From'          = 'GatheredFrom'
                'Gathered LogName'       = 'GatheredLogName'
            }
        }
        Events    = @{
            Enabled     = $true
            Events      = 4720, 4738
            LogName     = 'Security'
            Fields      = [ordered] @{
                'Computer'            = 'Domain Controller'
                'Action'              = 'Action'
                'ObjectAffected'      = 'User Affected'
                'SamAccountName'      = 'SamAccountName'
                'DisplayName'         = 'DisplayName'
                'UserPrincipalName'   = 'UserPrincipalName'
                'HomeDirectory'       = 'Home Directory'
                'HomePath'            = 'Home Path'
                'ScriptPath'          = 'Script Path'
                'ProfilePath'         = 'Profile Path'
                'UserWorkstations'    = 'User Workstations'
                'PasswordLastSet'     = 'Password Last Set'
                'AccountExpires'      = 'Account Expires'
                'PrimaryGroupId'      = 'Primary Group Id'
                'AllowedToDelegateTo' = 'Allowed To Delegate To'
                'OldUacValue'         = 'Old Uac Value'
                'NewUacValue'         = 'New Uac Value'
                'UserAccountControl'  = 'User Account Control'
                'UserParameters'      = 'User Parameters'
                'SidHistory'          = 'Sid History'
                'Who'                 = 'Who'
                'Date'                = 'When'
                # Common Fields
                'ID'                  = 'Event ID'
                'RecordID'            = 'Record ID'
                'GatheredFrom'        = 'Gathered From'
                'GatheredLogName'     = 'Gathered LogName'
            }
            Ignore      = @{
                # Cleanup Anonymous LOGON (usually related to password events)
                # https://social.technet.microsoft.com/Forums/en-US/5b2a93f7-7101-43c1-ab53-3a51b2e05693/eventid-4738-user-account-was-changed-by-anonymous?forum=winserverDS
                SubjectUserName = "ANONYMOUS LOGON"

                # Test value
                #ProfilePath = 'C*'
            }
            Functions   = @{
                'ProfilePath'        = 'Convert-UAC'
                'OldUacValue'        = 'Remove-WhiteSpace', 'Convert-UAC'
                'NewUacValue'        = 'Remove-WhiteSpace', 'Convert-UAC'
                'UserAccountControl' = 'Remove-WhiteSpace', 'Split-OnSpace', 'Convert-UAC'
            }
            IgnoreWords = @{
                #'Profile Path' = 'TEMP*'
            }
            SortBy      = 'When'
        }
    }
    UserChangesDetailed     = [ordered] @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 5136, 5137, 5141
            LogName     = 'Security'
            Filter      = @{
                'ObjectClass' = 'user'
            }
            Functions   = @{
                'OperationType' = 'ConvertFrom-OperationType'
            }
            Fields      = [ordered] @{
                'Computer'                 = 'Domain Controller'
                'Action'                   = 'Action'
                'OperationType'            = 'Action Detail'
                'Who'                      = 'Who'
                'Date'                     = 'When'
                'ObjectDN'                 = 'User Object'
                'AttributeLDAPDisplayName' = 'Field Changed'
                'AttributeValue'           = 'Field Value'
                # Common Fields
                'RecordID'                 = 'Record ID'
                'ID'                       = 'Event ID'
                'GatheredFrom'             = 'Gathered From'
                'GatheredLogName'          = 'Gathered LogName'
            }

            SortBy      = 'Record ID'
            Descending  = $false
            IgnoreWords = @{

            }
        }
    }
    ComputerChangesDetailed = [ordered] @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 5136, 5137, 5141
            LogName     = 'Security'
            Filter      = @{
                'ObjectClass' = 'computer'
            }
            Functions   = @{
                'OperationType' = 'ConvertFrom-OperationType'
            }
            Fields      = [ordered] @{
                'Computer'                 = 'Domain Controller'
                'Action'                   = 'Action'
                'OperationType'            = 'Action Detail'
                'Who'                      = 'Who'
                'Date'                     = 'When'
                'ObjectDN'                 = 'Computer Object'
                'AttributeLDAPDisplayName' = 'Field Changed'
                'AttributeValue'           = 'Field Value'
                # Common Fields
                'RecordID'                 = 'Record ID'
                'ID'                       = 'Event ID'
                'GatheredFrom'             = 'Gathered From'
                'GatheredLogName'          = 'Gathered LogName'
            }
            SortBy      = 'Record ID'
            Descending  = $false
            IgnoreWords = @{}
        }
    }
    UserStatus              = @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 4722, 4725, 4767, 4723, 4724, 4726
            LogName     = 'Security'
            IgnoreWords = @{}
            Fields      = [ordered] @{
                'Computer'        = 'Domain Controller'
                'Action'          = 'Action'
                'Who'             = 'Who'
                'Date'            = 'When'
                'ObjectAffected'  = 'User Affected'
                # Common Fields
                'ID'              = 'Event ID'
                'RecordID'        = 'Record ID'
                'GatheredFrom'    = 'Gathered From'
                'GatheredLogName' = 'Gathered LogName'
            }
            SortBy      = 'When'
        }
    }
    UserLockouts            = @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 4740
            LogName     = 'Security'
            IgnoreWords = @{}
            Fields      = [ordered] @{
                'Computer'         = 'Domain Controller'
                'Action'           = 'Action'
                'TargetDomainName' = 'Computer Lockout On'
                'ObjectAffected'   = 'User Affected'
                'Who'              = 'Reported By'
                'Date'             = 'When'
                # Common Fields
                'ID'               = 'Event ID'
                'RecordID'         = 'Record ID'
                'GatheredFrom'     = 'Gathered From'
                'GatheredLogName'  = 'Gathered LogName'
            }
            SortBy      = 'When'
        }
    }
    UserLogon               = @{
        Enabled = $false
        Events  = @{
            Enabled     = $false
            Events      = 4624
            LogName     = 'Security'
            Fields      = [ordered] @{
                'Computer'           = 'Computer'
                'Action'             = 'Action'
                'IpAddress'          = 'IpAddress'
                'IpPort'             = 'IpPort'
                'ObjectAffected'     = 'User / Computer Affected'
                'Who'                = 'Who'
                'Date'               = 'When'
                'LogonProcessName'   = 'LogonProcessName'
                'ImpersonationLevel' = 'ImpersonationLevel' # %%1833 = Impersonation
                'VirtualAccount'     = 'VirtualAccount'  # %%1843 = No
                'ElevatedToken'      = 'ElevatedToken' # %%1842 = Yes
                'LogonType'          = 'LogonType'
                # Common Fields
                'ID'                 = 'Event ID'
                'RecordID'           = 'Record ID'
                'GatheredFrom'       = 'Gathered From'
                'GatheredLogName'    = 'Gathered LogName'
            }
            IgnoreWords = @{}
        }
    }
    UserUnlocked            = @{
        # 4767 A user account was unlocked
        # https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=4767
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 4767
            LogName     = 'Security'
            IgnoreWords = @{}
            Functions   = @{}
            Fields      = [ordered] @{
                'Computer'         = 'Domain Controller'
                'Action'           = 'Action'
                'TargetDomainName' = 'Computer Lockout On'
                'ObjectAffected'   = 'User Affected'
                'Who'              = 'Who'
                'Date'             = 'When'
                # Common Fields
                'ID'               = 'Event ID'
                'RecordID'         = 'Record ID'
                'GatheredFrom'     = 'Gathered From'
                'GatheredLogName'  = 'Gathered LogName'
            }
            SortBy      = 'When'
        }
    }
    ComputerCreatedChanged  = @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 4741, 4742 # created, changed
            LogName     = 'Security'
            Ignore      = @{
                # Cleanup Anonymous LOGON (usually related to password events)
                # https://social.technet.microsoft.com/Forums/en-US/5b2a93f7-7101-43c1-ab53-3a51b2e05693/eventid-4738-user-account-was-changed-by-anonymous?forum=winserverDS
                SubjectUserName = "ANONYMOUS LOGON"
            }
            Fields      = [ordered] @{
                'Computer'            = 'Domain Controller'
                'Action'              = 'Action'
                'ObjectAffected'      = 'Computer Affected'
                'SamAccountName'      = 'SamAccountName'
                'DisplayName'         = 'DisplayName'
                'UserPrincipalName'   = 'UserPrincipalName'
                'HomeDirectory'       = 'Home Directory'
                'HomePath'            = 'Home Path'
                'ScriptPath'          = 'Script Path'
                'ProfilePath'         = 'Profile Path'
                'UserWorkstations'    = 'User Workstations'
                'PasswordLastSet'     = 'Password Last Set'
                'AccountExpires'      = 'Account Expires'
                'PrimaryGroupId'      = 'Primary Group Id'
                'AllowedToDelegateTo' = 'Allowed To Delegate To'
                'OldUacValue'         = 'Old Uac Value'
                'NewUacValue'         = 'New Uac Value'
                'UserAccountControl'  = 'User Account Control'
                'UserParameters'      = 'User Parameters'
                'SidHistory'          = 'Sid History'
                'Who'                 = 'Who'
                'Date'                = 'When'
                # Common Fields
                'ID'                  = 'Event ID'
                'RecordID'            = 'Record ID'
                'GatheredFrom'        = 'Gathered From'
                'GatheredLogName'     = 'Gathered LogName'
            }
            IgnoreWords = @{}
        }
    }
    ComputerDeleted         = @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 4743 # deleted
            LogName     = 'Security'
            IgnoreWords = @{}
            Fields      = [ordered] @{
                'Computer'        = 'Domain Controller'
                'Action'          = 'Action'
                'ObjectAffected'  = 'Computer Affected'
                'Who'             = 'Who'
                'Date'            = 'When'
                # Common Fields
                'ID'              = 'Event ID'
                'RecordID'        = 'Record ID'
                'GatheredFrom'    = 'Gathered From'
                'GatheredLogName' = 'Gathered LogName'
            }
            SortBy      = 'When'
        }
    }
    UserLogonKerberos       = @{
        Enabled = $false
        Events  = @{
            Enabled     = $false
            Events      = 4768
            LogName     = 'Security'
            IgnoreWords = @{}
            Functions   = @{
                'IpAddress' = 'Clean-IpAddress'
            }
            Fields      = [ordered] @{
                'Computer'             = 'Domain Controller'
                'Action'               = 'Action'
                'ObjectAffected'       = 'Computer/User Affected'
                'IpAddress'            = 'IpAddress'
                'IpPort'               = 'Port'
                'TicketOptions'        = 'TicketOptions'
                'Status'               = 'Status'
                'TicketEncryptionType' = 'TicketEncryptionType'
                'PreAuthType'          = 'PreAuthType'
                'Date'                 = 'When'

                # Common Fields
                'ID'                   = 'Event ID'
                'RecordID'             = 'Record ID'
                'GatheredFrom'         = 'Gathered From'
                'GatheredLogName'      = 'Gathered LogName'
            }
            SortBy      = 'When'
        }
    }
    GroupMembershipChanges  = @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 4728, 4729, 4732, 4733, 4746, 4747, 4751, 4752, 4756, 4757, 4761, 4762, 4785, 4786, 4787, 4788
            LogName     = 'Security'
            IgnoreWords = @{
                #'Who' = '*ANONYMOUS*'
            }
            Fields      = [ordered] @{
                'Computer'            = 'Domain Controller'
                'Action'              = 'Action'
                'TargetUserName'      = 'Group Name'
                'MemberNameWithoutCN' = 'Member Name'
                'Who'                 = 'Who'
                'Date'                = 'When'

                # Common Fields
                'ID'                  = 'Event ID'
                'RecordID'            = 'Record ID'
                'GatheredFrom'        = 'Gathered From'
                'GatheredLogName'     = 'Gathered LogName'
            }
            SortBy      = 'When'
        }
    }
    GroupEnumeration        = @{
        Enabled = $false
        Events  = @{
            Enabled     = $true
            Events      = 4798, 4799
            LogName     = 'Security'
            IgnoreWords = @{
                #'Who' = '*ANONYMOUS*'
            }
            Fields      = [ordered] @{
                'Computer'        = 'Domain Controller'
                'Action'          = 'Action'
                'TargetUserName'  = 'Group Name'
                'Who'             = 'Who'
                'Date'            = 'When'
                # Common Fields
                'ID'              = 'Event ID'
                'RecordID'        = 'Record ID'
                'GatheredFrom'    = 'Gathered From'
                'GatheredLogName' = 'Gathered LogName'
            }
            SortBy      = 'When'
        }
    }
    GroupChanges            = @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 4735, 4737, 4745, 4750, 4760, 4764, 4784, 4791
            LogName     = 'Security'
            IgnoreWords = @{
                'Who' = '*ANONYMOUS*'
            }

            Fields      = [ordered] @{
                'Computer'        = 'Domain Controller'
                'Action'          = 'Action'
                'TargetUserName'  = 'Group Name'
                'Who'             = 'Who'
                'Date'            = 'When'
                'GroupTypeChange' = 'Changed Group Type'
                'SamAccountName'  = 'Changed SamAccountName'
                'SidHistory'      = 'Changed SidHistory'

                # Common Fields
                'ID'              = 'Event ID'
                'RecordID'        = 'Record ID'
                'GatheredFrom'    = 'Gathered From'
                'GatheredLogName' = 'Gathered LogName'
            }

            SortBy      = 'When'
        }
    }
    GroupCreateDelete       = @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 4727, 4730, 4731, 4734, 4744, 4748, 4749, 4753, 4754, 4758, 4759, 4763
            LogName     = 'Security'
            IgnoreWords = @{
                # 'Who' = '*ANONYMOUS*'
            }
            Fields      = [ordered] @{
                'Computer'        = 'Domain Controller'
                'Action'          = 'Action'
                'TargetUserName'  = 'Group Name'
                'Who'             = 'Who'
                'Date'            = 'When'
                # Common Fields
                'ID'              = 'Event ID'
                'RecordID'        = 'Record ID'
                'GatheredFrom'    = 'Gathered From'
                'GatheredLogName' = 'Gathered LogName'
            }
            SortBy      = 'When'
        }
    }
    GroupChangesDetailed    = [ordered] @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 5136, 5137, 5141
            LogName     = 'Security'
            Filter      = @{
                # Filter is special
                # if there is just one object on the right side it will filter on that field
                # if there are more objects filter will pick all values on the right side and display them (using AND)
                'ObjectClass' = 'group'
            }
            Functions   = @{
                'OperationType' = 'ConvertFrom-OperationType'
            }
            Fields      = [ordered] @{
                'Computer'                 = 'Domain Controller'
                'Action'                   = 'Action'
                'OperationType'            = 'Action Detail'
                'Who'                      = 'Who'
                'Date'                     = 'When'
                'ObjectDN'                 = 'Computer Object'
                'ObjectClass'              = 'ObjectClass'
                'AttributeLDAPDisplayName' = 'Field Changed'
                'AttributeValue'           = 'Field Value'
                # Common Fields
                'RecordID'                 = 'Record ID'
                'ID'                       = 'Event ID'
                'GatheredFrom'             = 'Gathered From'
                'GatheredLogName'          = 'Gathered LogName'
            }

            SortBy      = 'Record ID'
            Descending  = $false
            IgnoreWords = @{

            }
        }
    }
    GroupPolicyChanges      = [ordered] @{
        Enabled                     = $true
        'Group Policy Name Changes' = @{
            Enabled     = $true
            Events      = 5136, 5137, 5141
            LogName     = 'Security'
            Filter      = @{
                # Filter is special, if there is just one object on the right side
                # If there are more objects filter will pick all values on the right side and display them as required
                'ObjectClass'              = 'groupPolicyContainer'
                #'OperationType' = 'Value Added'
                'AttributeLDAPDisplayName' = $null, 'displayName' #, 'versionNumber'
            }
            Functions   = @{
                'OperationType' = 'ConvertFrom-OperationType'
            }
            Fields      = [ordered] @{
                'RecordID'                 = 'Record ID'
                'Computer'                 = 'Domain Controller'
                'Action'                   = 'Action'
                'Who'                      = 'Who'
                'Date'                     = 'When'


                'ObjectDN'                 = 'ObjectDN'
                'ObjectGUID'               = 'ObjectGUID'
                'ObjectClass'              = 'ObjectClass'
                'AttributeLDAPDisplayName' = 'AttributeLDAPDisplayName'
                #'AttributeSyntaxOID' = 'AttributeSyntaxOID'
                'AttributeValue'           = 'AttributeValue'
                'OperationType'            = 'OperationType'
                'OpCorrelationID'          = 'OperationCorelationID'
                'AppCorrelationID'         = 'OperationApplicationCorrelationID'

                'DSName'                   = 'DSName'
                'DSType'                   = 'DSType'
                'Task'                     = 'Task'
                'Version'                  = 'Version'

                # Common Fields
                'ID'                       = 'Event ID'

                'GatheredFrom'             = 'Gathered From'
                'GatheredLogName'          = 'Gathered LogName'
            }

            SortBy      = 'Record ID'
            Descending  = $false
            IgnoreWords = @{

            }
        }
        'Group Policy Edits'        = @{
            Enabled     = $true
            Events      = 5136, 5137, 5141
            LogName     = 'Security'
            Filter      = @{
                # Filter is special, if there is just one object on the right side
                # If there are more objects filter will pick all values on the right side and display them as required
                'ObjectClass'              = 'groupPolicyContainer'
                #'OperationType' = 'Value Added'
                'AttributeLDAPDisplayName' = 'versionNumber'
            }
            Functions   = @{
                'OperationType' = 'ConvertFrom-OperationType'
            }
            Fields      = [ordered] @{
                'RecordID'                 = 'Record ID'
                'Computer'                 = 'Domain Controller'
                'Action'                   = 'Action'
                'Who'                      = 'Who'
                'Date'                     = 'When'


                'ObjectDN'                 = 'ObjectDN'
                'ObjectGUID'               = 'ObjectGUID'
                'ObjectClass'              = 'ObjectClass'
                'AttributeLDAPDisplayName' = 'AttributeLDAPDisplayName'
                #'AttributeSyntaxOID' = 'AttributeSyntaxOID'
                'AttributeValue'           = 'AttributeValue'
                'OperationType'            = 'OperationType'
                'OpCorrelationID'          = 'OperationCorelationID'
                'AppCorrelationID'         = 'OperationApplicationCorrelationID'

                'DSName'                   = 'DSName'
                'DSType'                   = 'DSType'
                'Task'                     = 'Task'
                'Version'                  = 'Version'

                # Common Fields
                'ID'                       = 'Event ID'

                'GatheredFrom'             = 'Gathered From'
                'GatheredLogName'          = 'Gathered LogName'
            }

            SortBy      = 'Record ID'
            Descending  = $false
            IgnoreWords = @{

            }
        }
        'Group Policy Links'        = @{
            Enabled     = $true
            Events      = 5136, 5137, 5141
            LogName     = 'Security'
            Filter      = @{
                # Filter is special, if there is just one object on the right side
                # If there are more objects filter will pick all values on the right side and display them as required
                'ObjectClass' = 'domainDNS'
                #'OperationType' = 'Value Added'
                #'AttributeLDAPDisplayName' = 'versionNumber'
            }
            Functions   = @{
                'OperationType' = 'ConvertFrom-OperationType'
            }
            Fields      = [ordered] @{
                'RecordID'                 = 'Record ID'
                'Computer'                 = 'Domain Controller'
                'Action'                   = 'Action'
                'Who'                      = 'Who'
                'Date'                     = 'When'


                'ObjectDN'                 = 'ObjectDN'
                'ObjectGUID'               = 'ObjectGUID'
                'ObjectClass'              = 'ObjectClass'
                'AttributeLDAPDisplayName' = 'AttributeLDAPDisplayName'
                #'AttributeSyntaxOID' = 'AttributeSyntaxOID'
                'AttributeValue'           = 'AttributeValue'
                'OperationType'            = 'OperationType'
                'OpCorrelationID'          = 'OperationCorelationID'
                'AppCorrelationID'         = 'OperationApplicationCorrelationID'

                'DSName'                   = 'DSName'
                'DSType'                   = 'DSType'
                'Task'                     = 'Task'
                'Version'                  = 'Version'

                # Common Fields
                'ID'                       = 'Event ID'

                'GatheredFrom'             = 'Gathered From'
                'GatheredLogName'          = 'Gathered LogName'
            }

            SortBy      = 'Record ID'
            Descending  = $false
            IgnoreWords = @{

            }
        }
    }
    LogsClearedSecurity     = @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 1102, 1105
            LogName     = 'Security'
            Fields      = [ordered] @{
                'Computer'        = 'Domain Controller'
                'Action'          = 'Action'
                'BackupPath'      = 'Backup Path'
                'Channel'         = 'Log Type'

                'Who'             = 'Who'
                'Date'            = 'When'

                # Common Fields
                'ID'              = 'Event ID'
                'RecordID'        = 'Record ID'
                'GatheredFrom'    = 'Gathered From'
                'GatheredLogName' = 'Gathered LogName'
                #'Test' = 'Test'
            }
            SortBy      = 'When'
            IgnoreWords = @{}
            Overwrite   = @{
                # Allows to overwrite field content on the fly, either only on IF or IF ELSE
                # IF <VALUE> -eq <VALUE> THEN <VALUE> (3 VALUES)
                # IF <VALUE> -eq <VALUE> THEN <VALUE> ELSE <VALUE> (4 VALUES)
                # If you need to use IF multiple times for same field use spaces to distinguish HashTable Key.

                'Backup Path' = 'Backup Path', '', 'N/A'
                #'Backup Path ' = 'Backup Path', 'C:\Windows\System32\Winevt\Logs\Archive-Security-2018-11-24-09-25-36-988.evtx', 'MMMM'
                'Who'         = 'Event ID', 1105, 'Automatic Backup'  # if event id 1105 set field to Automatic Backup
                #'Test' = 'Event ID', 1106, 'Test', 'Mama mia'
            }
        }
    }
    LogsClearedOther        = @{
        Enabled = $true
        Events  = @{
            Enabled     = $true
            Events      = 104
            LogName     = 'System'
            IgnoreWords = @{}
            Fields      = [ordered] @{
                'Computer'     = 'Domain Controller'
                'Action'       = 'Action'
                'BackupPath'   = 'Backup Path'
                'Channel'      = 'Log Type'

                'Who'          = 'Who'
                'Date'         = 'When'

                # Common Fields
                'ID'           = 'Event ID'
                'RecordID'     = 'Record ID'
                'GatheredFrom' = 'Gathered From'
            }
            SortBy      = 'When'
            Overwrite   = @{
                # Allows to overwrite field content on the fly, either only on IF or IF ELSE
                # IF <VALUE> -eq <VALUE> THEN <VALUE> (3 VALUES)
                # IF <VALUE> -eq <VALUE> THEN <VALUE> ELSE <VALUE> (4 VALUES)
                # If you need to use IF multiple times for same field use spaces to distinguish HashTable Key.

                'Backup Path' = 'Backup Path', '', 'N/A'
            }
        }
    }
    EventsReboots           = @{
        Enabled = $false
        Events  = @{
            Enabled     = $true
            Events      = 1001, 1018, 1, 12, 13, 42, 41, 109, 1, 6005, 6006, 6008, 6013
            LogName     = 'System'
            IgnoreWords = @{

            }
        }
    }
}
$Script:ReportTimes = @{
    # Report Per Hour
    PastHour             = @{
        Enabled = $false # if it's 23:22 it will report 22:00 till 23:00
    }
    CurrentHour          = @{
        Enabled = $false # if it's 23:22 it will report 23:00 till 00:00
    }
    # Report Per Day
    PastDay              = @{
        Enabled = $false # if it's 1.04.2018 it will report 31.03.2018 00:00:00 till 01.04.2018 00:00:00
    }
    CurrentDay           = @{
        Enabled = $false # if it's 1.04.2018 05:22 it will report 1.04.2018 00:00:00 till 01.04.2018 00:00:00
    }
    # Report Per Week
    OnDay                = @{
        Enabled = $false
        Days    = 'Monday'#, 'Tuesday'
    }
    # Report Per Month
    PastMonth            = @{
        Enabled = $false # checks for 1st day of the month - won't run on any other day unless used force
        Force   = $true  # if true - runs always ...
    }
    CurrentMonth         = @{
        Enabled = $false
    }

    # Report Per Quarter
    PastQuarter          = @{
        Enabled = $false # checks for 1st day fo the quarter - won't run on any other day
        Force   = $true
    }
    CurrentQuarter       = @{
        Enabled = $false
    }
    # Report Custom
    CurrentDayMinusDayX  = @{
        Enabled = $false
        Days    = 7    # goes back X days and shows just 1 day
    }
    CurrentDayMinuxDaysX = @{
        Enabled = $false
        Days    = 3 # goes back X days and shows X number of days till Today
    }
    CustomDate           = @{
        Enabled  = $false
        DateFrom = get-date -Year 2018 -Month 03 -Day 19
        DateTo   = get-date -Year 2018 -Month 03 -Day 23
    }
    Last3days            = @{
        Enabled = $false
    }
    Last7days            = @{
        Enabled = $false
    }
    Last14days           = @{
        Enabled = $false

    }
    Everything           = @{
        Enabled = $false
    }
}
function Send-Notificaton {
    [CmdletBinding()]
    param(
        [PSCustomObject] $Events,
        [Parameter(Mandatory = $true)][alias('ReportOptions')][System.Collections.IDictionary] $Options,
        [string] $Priority = 'Default'
    )
    Begin {}
    Process {
        if ($Events -ne $null) {
            foreach ($Event in $Events) {
                [string] $MessageTitle = 'Active Directory Changes'
                [string] $ActivityTitle = $($Event.Action).Trim()


                # Building message
                $Teams = Get-NotificationParameters -Type 'Microsoft Teams' -Notifications $Options.Notifications.MicrosoftTeams -ActivityTitle $ActivityTitle -Priority $Priority
                $Slack = Get-NotificationParameters -Type 'Slack' -Notifications $Options.Notifications.Slack -ActivityTitle $ActivityTitle -Priority $Priority
                $Discord = Get-NotificationParameters -Type 'Discord' -Notifications $Options.Notifications.Discord -ActivityTitle $ActivityTitle -Priority $Priority

                $FactsSlack = @()
                $FactsTeams = @()
                $FactsDiscord = @()
                foreach ($Property in $event.PSObject.Properties) {
                    if ($Property.Value -ne $null -and $Property.Value -ne '') {
                        if ($Property.Name -eq 'When') {
                            $FactsTeams += New-TeamsFact -Name $Property.Name -Value $Property.Value.DateTime
                            $FactsSlack += @{ title = $Property.Name; value = $Property.Value.DateTime; short = $true }
                            $FactsDiscord += New-DiscordFact -Name $Property.Name -Value $Property.Value.DateTime -Inline $true
                        } else {
                            $FactsTeams += New-TeamsFact -Name $Property.Name -Value $Property.Value
                            $FactsSlack += @{ title = $Property.Name; value = $Property.Value; short = $true }
                            $FactsDiscord += New-DiscordFact -Name $Property.Name -Value $Property.Value -Inline $true
                        }
                    }
                }

                # Slack Notifications
                if ($Options.Notifications.Slack.Enabled) {
                    $SlackChannel = $Options.Notifications.Slack.$Priority.Channel
                    $SlackColor = ConvertFrom-Color -Color $Slack.Color

                    $Data = New-SlackMessageAttachment -Color $SlackColor `
                        -Title "$MessageTitle - $ActivityTitle"  `
                        -Fields $FactsSlack `
                        -Fallback $ActivityTitle |
                        New-SlackMessage -Channel $SlackChannel `
                        -IconEmoji :bomb:  |
                        Send-SlackMessage -Uri $Slack.Uri -Verbose

                    Write-Color @script:WriteParameters -Text "[i] Slack output: ", $Data -Color White, Yellow
                }
                # Microsoft Teams Nofications
                if ($Options.Notifications.MicrosoftTeams.Enabled) {

                    $Section1 = New-TeamsSection `
                        -ActivityTitle $ActivityTitle `
                        -ActivityImageLink $Teams.ActivityImageLink `
                        -ActivityDetails $FactsTeams

                    $Data = Send-TeamsMessage `
                        -URI $Teams.Uri `
                        -MessageTitle $MessageTitle `
                        -Color $Teams.Color `
                        -Sections $Section1 `
                        -Supress $false `
                        -MessageSummary $ActivityTitle
                    # -Verbose
                    Write-Color @script:WriteParameters -Text "[i] Teams output: ", $Data -Color White, Yellow
                }
                # Discord Notifications
                if ($Options.Notifications.Discord.Enabled) {
                    $Thumbnail = New-DiscordImage -Url $Discord.ActivityImageLink

                    $Section1 = New-DiscordSection `
                        -Title $ActivityTitle `
                        -Facts $FactsDiscord `
                        -Thumbnail $Thumbnail `
                        -Color $Discord.Color -Verbose

                    $Data = Send-DiscordMessage `
                        -WebHookUrl $Discord.Uri `
                        -Sections $Section1 `
                        -AvatarName $Discord.AvatarName `
                        -AvatarUrl $Discord.AvatarUrl `
                        -OutputJSON

                    Write-Color @script:WriteParameters -Text "[i] Discord output: ", $Data -Color White, Yellow
                }
                if ($Options.Notifications.MSSQL.Enabled) {
                    $SqlQuery = Send-SqlInsert -Object $Events -SqlSettings $Options.Notifications.MSSQL -Verbose:$Options.Debug.Verbose
                    foreach ($Query in $SqlQuery) {
                        Write-Color @script:WriteParameters -Text '[i] ', 'MS SQL Output: ', $Query -Color White, White, Yellow
                    }
                }
                if ($Options.Notifications.Email.Enabled) {
                    # Prepare email body
                    $Logger.AddInfoRecord('Prepare email head and body')
                    $HtmlHead = Set-EmailHead -FormattingOptions $Options.AsHTML.Formatting
                    $HtmlBody = Set-EmailReportBranding -FormattingParameters $Options.AsHTML.Formatting
                    #$HtmlBody += Set-EmailReportDetails -FormattingParameters $Options.AsHTML.Formatting -Dates $Dates -Warnings $Warnings

                    $HtmlBody += Export-ReportToHTML -Report $true-ReportTable $Events -ReportTableText "Quick notification event"

                    # Do Cleanup of Emails
                    #$HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateDays**' -ReplaceWith $time.Elapsed.Days
                    #$HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateHours**' -ReplaceWith $time.Elapsed.Hours
                    #$HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateMinutes**' -ReplaceWith $time.Elapsed.Minutes
                    #$HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateSeconds**' -ReplaceWith $time.Elapsed.Seconds
                    #$HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateMilliseconds**' -ReplaceWith $time.Elapsed.Milliseconds
                    $HtmlBody = Set-EmailFormatting -Template $HtmlBody -FormattingParameters $Options.AsHTML.Formatting -ConfigurationParameters $Options -Logger $Logger -SkipNewLines

                    $HTML = $HtmlHead + $HtmlBody
                    #$ReportHTMLPath = Set-ReportFileName -ReportOptions $Options -ReportExtension 'html'
                    $ReportHTMLPath = Set-ReportFile -Path $Env:TEMP -FileNamePattern 'PSWinReporting.html' -DateFormat $null
                    try {
                        $HTML | Out-File -Encoding Unicode -FilePath $ReportHTMLPath -ErrorAction Stop
                        $Logger.AddInfoRecord("Saving report to file: $ReportHTMLPath")
                        if ($Options.SendMail.Attach.HTML) {
                            $AttachHTML += $ReportHTMLPath
                            $AttachedReports += $ReportHTMLPath
                        }
                    } catch {
                        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                        $Logger.AddErrorRecord("Error saving file $ReportHTMLPath.")
                        $Logger.AddErrorRecord("Error: $ErrorMessage")
                    }

                    $TemporarySubject = $Options.SendMail.Parameters.Subject -replace "<<DateFrom>>", "$($Dates.DateFrom)" -replace "<<DateTo>>", "$($Dates.DateTo)"
                    $Logger.AddInfoRecord('Sending email with reports')
                    if ($Options.Notifications.Email.Formatting.CompanyBranding.Inline) {
                        $SendMail = Send-Email -EmailParameters $Options.SendMail.Parameters -Body $EmailBody -Attachment $AttachedReports -Subject $TemporarySubject -InlineAttachments @{logo = $Options.AsHTML.Formatting.CompanyBranding.Logo} -Logger $Logger
                    } else {
                        $SendMail = Send-Email -EmailParameters $Options.SendMail.Parameters -Body $EmailBody -Attachment $AttachedReports -Subject $TemporarySubject -Logger $Logger
                    }
                    if ($SendMail.Status) {
                        $Logger.AddInfoRecord('Email successfully sent')
                    } else {
                        $Logger.AddInfoRecord("Error sending message: $($SendMail.Error)")
                    }
                    Remove-ReportFiles -KeepReports $false -ReportFiles
                }
            }
        }
    }
    End {}
}
function Set-DisplayParameters($ReportOptions, $DisplayProgress = $false) {
    $Test0 = Test-Key -ConfigurationTable $ReportOptions -ConfigurationKey 'DisplayConsole' -DisplayProgress $DisplayProgress
    if ($Test0 -eq $true) {
        $Test1 = Test-Key -ConfigurationTable $ReportOptions.DisplayConsole -ConfigurationSection '' -ConfigurationKey "ShowTime" -DisplayProgress $DisplayProgress
        $Test2 = Test-Key -ConfigurationTable $ReportOptions.DisplayConsole -ConfigurationSection '' -ConfigurationKey "LogFile" -DisplayProgress $DisplayProgress
        $Test3 = Test-Key -ConfigurationTable $ReportOptions.DisplayConsole -ConfigurationSection '' -ConfigurationKey "TimeFormat" -DisplayProgress $DisplayProgress

        if ($Test1 -and $Test2 -and $Test3) { $script:WriteParameters = $ReportOptions.DisplayConsole }
    }
}
function Set-EmailReportDetails($FormattingParameters, $Dates, $Warnings) {
    $DateReport = get-date
    # HTML Report settings
    $Report = "<p style=`"background-color:white;font-family:$($FormattingParameters.FontFamily);font-size:$($FormattingParameters.FontSize)`">" +
    "<strong>Report Time:</strong> $DateReport <br>" +
    "<strong>Report Period:</strong> $($Dates.DateFrom) to $($Dates.DateTo) <br>" +
    "<strong>Account Executing Report :</strong> $env:userdomain\$($env:username.toupper()) on $($env:ComputerName.toUpper()) <br>" +
    "<strong>Time to generate:</strong> **TimeToGenerateDays** days, **TimeToGenerateHours** hours, **TimeToGenerateMinutes** minutes, **TimeToGenerateSeconds** seconds, **TimeToGenerateMilliseconds** milliseconds"

    if ($($Warnings | Measure-Object).Count -gt 0) {
        $Report += "<br><br><strong>Warnings:</strong>"
        foreach ($warning in $Warnings) {
            $Report += "<br> $warning"
        }
    }
    $Report += "</p>"
    return $Report
}
function Set-MissingDescription {
    param(

    )
    $AllSubscriptions = Start-MyProgram -Program $ProgramWecutil -cmdArgList 'es'

    foreach ($Subscription in $AllSubscriptions) {
        $SubData = Start-MyProgram -Program $ProgramWecutil -cmdArgList 'gs', $Subscription
        Find-MyProgramData -Data $SubData -FindText 'ContentFormat*'

        $Change = Start-MyProgram -Program $ProgramWecutil -cmdArgList 'ss', $Subscription, '/cf:Events'
    }
}
function Set-ReportFile {
    param(
        [string] $Path,
        [alias('FilePattern')][string] $FileNamePattern,
        [string] $DateFormat,
        [string] $Extension,
        [string] $ReportName
    )
    $FileNamePattern = $FileNamePattern.Replace('<currentdate>', $(get-date -f $DateFormat))
    $FileNamePattern = $FileNamePattern.Replace('<extension>', $Extension)
    $FileNamePattern = $FileNamePattern.Replace('<reportname>', $ReportName)
    return "$Path\$FileNamePattern"
}
function Set-ReportFileName {
    param(
        [System.Collections.IDictionary] $ReportOptions,
        [string] $ReportExtension,
        [string] $ReportName = ""
    )
    if ($ReportOptions.KeepReportsPath -ne "") {
        $Path = $ReportOptions.KeepReportsPath
    } else {
        $Path = $env:TEMP
    }
    $ReportPath = $Path + "\" + $ReportOptions.FilePattern
    $ReportPath = $ReportPath -replace "<currentdate>", $(get-date -f $ReportOptions.FilePatternDateFormat)
    if ($ReportName -ne "") {
        $ReportPath = $ReportPath.Replace(".<extension>", "-$ReportName.$ReportExtension")
    } else {
        $ReportPath = $ReportPath.Replace(".<extension>", ".$ReportExtension")
    }
    return $ReportPath
}
function Set-ServersPermissions {
    [CmdletBinding()]
    param (
        $ProgramWevtutil,
        $Servers,
        [string]$LogName = 'security'
    )


    foreach ($DC in $Servers) {
        $cmdArgListGet = @(
            "gl"
            $LogName
            "/r:$DC"
        )
        $cmdArgListSet = @(
            "sl",
            $LogName
            "/r:$DC"
            "/ca:O:BAG:SYD:(A; ; 0xf0005; ; ; SY)(A; ; 0x5; ; ; BA)(A; ; 0x1; ; ; S-1-5-32-573)(A; ; 0x1; ; ; S-1-5-20)"
        )

        Start-MyProgram -Program $Script:ProgramWevtutil -cmdArgList $cmdArgListSet
        Start-MyProgram -Program $Script:ProgramWevtutil -cmdArgList $cmdArgListGet
    }
}
function Set-SubscriptionTemplates {
    [CmdletBinding()]
    param(
        [System.Array] $ListTemplates,
        [switch] $DeleteOwn,
        [switch] $DeleteAllOther,
        [System.Collections.IDictionary] $LoggerParameters
    )

    if (-not $LoggerParameters) {
        $LoggerParameters = $Script:LoggerParameters
    }
    $Logger = Get-Logger @LoggerParameters

    if ($DeleteAll -or $DeleteOwn) {
        Remove-Subscription -All:$DeleteAllOther -Own:$DeleteOwn
    }
    foreach ($TemplatePath in $ListTemplates) {
        #Write-Color 'Adding provider ', $TemplatePath, ' to Subscriptions.' -Color White, Green, White
        $Logger.AddInfoRecord("Adding provider $TemplatePath to Subscriptions.")
        Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'cs', $TemplatePath
    }
}
function Set-TimeReports {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $HashTable
    )
    # Get all report Names
    $Reports = @()
    foreach ($reportName in $($HashTable.GetEnumerator().Name)) {
        $Reports += $reportName
    }

    # Get Highest Count of servers
    $Count = 0
    foreach ($reportName in $reports) {
        if ($($HashTable[$reportName]).Count -ge $Count) {
            $Count = $($HashTable[$reportName]).Count
        }
    }
    $Count = $Count - 1 # Removes Total from Server Count

    $htmlStart = @"
    <table border="0" cellpadding="3" style="font-size:8pt;font-family:Segoe UI,Arial,sans-serif">
        <tr bgcolor="#009900">
            <th colspan="1">
                <font color="#ffffff">Report Names</font>
            </th>
            <th colspan="1">
                <font color="#ffffff">Total</font>
            </th>
        </tr>
"@


    foreach ($reportName in $reports) {
        $htmlStart += '<tr align="left" bgcolor="#dddddd">'
        $htmlStart += '<td>' + $reportName + '</td>'
        foreach ($ElapsedTime in $($HashTable[$reportName].GetEnumerator())) {
            $htmlStart += '<td>' + $ElapsedTime.Value + '</td>'
        }
        $htmlStart += '</tr>'
    }
    $htmlStart += '</table>'
    return $htmlStart
}
function Start-ADReporting () {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [System.Collections.IDictionary]$LoggerParameters,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$EmailParameters,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$FormattingParameters,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$ReportOptions,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$ReportTimes,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$ReportDefinitions
    )
    [bool] $WarningNoLogger = $false
    <#
        Set logger
    #>

    if (-not $LoggerParameters) {
        $LoggerParameters = $Script:LoggerParameters
        $WarningNoLogger = $true
    }

    $Params = @{
        LogPath    = if ([string]::IsNullOrWhiteSpace($LoggerParameters.LogsDir)) { '' } else { Join-Path $LoggerParameters.LogsDir "$([datetime]::Now.ToString('yyyy.MM.dd_hh.mm'))_ADReporting.log" }
        ShowTime   = $LoggerParameters.ShowTime
        TimeFormat = $LoggerParameters.TimeFormat
    }
    $Logger = Get-Logger @Params

    if ($WarningNoLogger) {
        $Logger.AddWarningRecord("New version of PSWinReporting requires Logger Parameter. Please read documentation. No logs will be written to disk.")
    }

    <#
        Test Configuration
    #>

    $Params = @{
        LoggerParameters     = $LoggerParameters
        EmailParameters      = $EmailParameters
        FormattingParameters = $FormattingParameters
        ReportOptions        = $ReportOptions
        ReportTimes          = $ReportTimes
        ReportDefinitions    = $ReportDefinitions
    }
    if (-not (Test-Configuration @Params)) {
        $Logger.AddErrorRecord("There are parameters missing in configuration file. Can't continue running.")
        exit
    }

    <#
        Test Modules
    #>

    if (-not (Test-Modules -ReportOptions $ReportOptions)) {
        $Logger.AddErrorRecord("Install the necessary modules. Can't continue running.")
    }

    if ($ReportOptions.JustTestPrerequisite) {
        exit
    }

    <#
        Test AD availability
    #>

    try {
        $null = Get-ADDomain
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        $Logger.AddErrorRecord("Failed to get AD domain information: $ErrorMessage)")
        exit
    }

    $Logger.AddInfoRecord('Starting to build a report')
    $Dates = Get-ChoosenDates -ReportTimes $ReportTimes
    foreach ($Date in $Dates) {
        Start-Report -Dates $Date -EmailParameters $EmailParameters -FormattingParameters $FormattingParameters -ReportOptions $ReportOptions -ReportDefinitions $ReportDefinitions
    }
}
function Start-Notifications {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Options,
        [System.Collections.IDictionary] $Definitions,
        [System.Collections.IDictionary] $Target,
        [int] $EventID,
        [int64] $EventRecordID,
        [string] $EventChannel
    )
    # Logger Setup
    if ($Options.Logging) {
        $LoggerParameters = $Options.Logging
    } else {
        $LoggerParameters = $Script:LoggerParameters
    }
    $Logger = Get-Logger @LoggerParameters

    $Results = @{}

    $Logger.AddInfoRecord("Executed Trigger for ID: $eventid and RecordID: $eventRecordID")
    $Logger.AddInfoRecord("Using Microsoft Teams: $($Options.Notifications.MicrosoftTeams.Enabled)")
    if ($Options.Notifications.MicrosoftTeams.Enabled) {
        foreach ($Priority in $Options.Notifications.MicrosoftTeams.Keys | Where-Object { $_ -notcontains 'Enabled' }) {
            [string] $URI = Format-FirstXChars -Text $Options.Notifications.MicrosoftTeams.$Priority.Uri -NumberChars 50
            $Logger.AddInfoRecord("Priority: $Priority, TeamsID: $URI...")
        }
    }
    $Logger.AddInfoRecord("Using Slack: $($Options.Notifications.Slack.Enabled)")
    if ($Options.Notifications.Slack.Enabled) {
        foreach ($Priority in $Options.Notifications.Slack.Keys | Where-Object { $_ -notcontains 'Enabled' }) {
            [string] $URI = Format-FirstXChars -Text $Options.Notifications.Slack.$Priority.URI -NumberChars 25
            $Logger.AddInfoRecord("Priority: $Priority, Slack URI: $URI...")
            $Logger.AddInfoRecord("Priority: $Priority, Slack Channel: $($($Options.Notifications.Slack.$Priority.Channel))...")
        }
    }
    $Logger.AddInfoRecord("Using Discord: $($Options.Notifications.Discord.Enabled)")
    if ($Options.Notifications.Discord.Enabled) {
        foreach ($Priority in $Options.Notifications.Discord.Keys | Where-Object { $_ -notcontains 'Enabled' }) {
            [string] $URI = Format-FirstXChars -Text $Options.Notifications.Discord.$Priority.URI -NumberChars 25
            $Logger.AddInfoRecord("Priority: $Priority, Discord URI: $URI...")
        }
    }
    $Logger.AddInfoRecord("Using MSSQL: $($Options.Notifications.MSSQL.Enabled)")
    if ($Options.Notifications.MSSQL.Enabled) {
        foreach ($Priority in $Options.Notifications.MSSQL.Keys | Where-Object { $_ -notcontains 'Enabled' }) {
            $Logger.AddInfoRecord("Priority: $Priority, Server\Instance: $($Options.Notifications.MSSQL.$Priority.SqlServer)")
            $Logger.AddInfoRecord("Priority: $Priority, Database: $($Options.Notifications.MSSQL.$Priority.SqlDatabase)")
        }
    }
    $Logger.AddInfoRecord("Using Email: $($Options.Notifications.Email.Enabled)")
    if ($Options.Notifications.Email.Enabled) {
        foreach ($Priority in $Options.Notifications.Email.Keys | Where-Object { 'Enabled', 'Formatting' -notcontains $_ }) {
            $Logger.AddInfoRecord("Priority: $Priority, Email TO: $($Options.Notifications.Email.$Priority.Parameters.To), Email CC: $($Options.Notifications.Email.$Priority.Parameters.CC)")
        }
    }

    if (-not $Options.Notifications.Slack.Enabled -and
        -not $Options.Notifications.MicrosoftTeams.Enabled -and
        -not $Options.Notifications.MSSQL.Enabled -and
        -not $Options.Notifications.Discord.Enabled -and
        -not $Options.Notifications.Email.Enabled) {
        # Terminating as no options are $true
        return
    }

    [Array] $ExtendedInput = Get-ServersListLimited -Target $Target -RecordID $EventRecordID
    foreach ($Entry in $ExtendedInput) {
        if ($Entry.Type -eq 'Computer') {
            $Logger.AddInfoRecord("Computer $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')")
        } else {
            $Logger.AddInfoRecord("File $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')")
        }
    }

    $AllEvents = Get-Events -ExtendedInput $ExtendedInput -EventID $eventid -RecordID $eventRecordID -Verbose:$Options.Debug.Verbose

    # Prepare the results based on chosen criteria
    foreach ($Report in  $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled' }) {
        if ($Definitions.$Report.Enabled) {
            #$ReportNameTitle = Format-AddSpaceToSentence -Text $Report -ToLowerCase
            $Logger.AddInfoRecord("Running $Report")
            $TimeExecution = Start-TimeLog
            foreach ($SubReport in $Definitions.$Report.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport'  }) {
                if ($Definitions.$Report.$SubReport.Enabled) {
                    $Logger.AddInfoRecord("Running $Report with subsection $SubReport")
                    [string] $EventsType = $Definitions.$Report.$SubReport.LogName
                    [Array] $EventsNeeded = $Definitions.$Report.$SubReport.Events
                    [Array] $EventsFound = Find-EventsNeeded -Events $AllEvents -EventIDs $EventsNeeded -EventsType $EventsType
                    [Array] $EventsFound = Get-EventsTranslation -Events $EventsFound -EventsDefinition $Definitions.$Report.$SubReport
                    $Logger.AddInfoRecord("Ending $Report with subsection $SubReport events found $($EventsFound.Count)")
                    $Results.$Report = $EventsFound
                }
            }
            $ElapsedTimeReport = Stop-TimeLog -Time $TimeExecution -Option OneLiner
            $Logger.AddInfoRecord("Ending $Report - Time to run $ElapsedTimeReport")
        }
    }
    [bool] $FoundPriorityEvent = $false
    foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport', 'Priority' }) {
        if ($Results.$ReportName) {
            if ($null -ne $Definitions.$ReportName.Priority) {
                foreach ($Priority in $Definitions.$ReportName.Priority.Keys) {
                    $MyValue = Find-EventsTo -Prioritize -Events $Results.$ReportName -DataSet  $Definitions.$ReportName.Priority.$Priority
                    if ((Get-ObjectCount -Object $MyValue) -gt 0) {
                        $Logger.AddInfoRecord("Sending event with $Priority priority.")
                        Send-Notificaton -Events $MyValue -Options $Options -Priority $Priority
                        $FoundPriorityEvent = $true
                    }
                }
            }
            if (-not $FoundPriorityEvent) {
                $Logger.AddInfoRecord("Sending event with default priority.")
                Send-Notificaton -Events $Results.$ReportName -Options $Options -Priority 'Default'
            }
        }
    }

    if ($Options.Backup.Enabled) {
        Protect-ArchivedLogs -TableEventLogClearedLogs $TableEventLogClearedLogs -DestinationPath $Options.Backup.DestinationPath -Verbose:$Options.Debug.Verbose
    }
}
function Start-Report {
    [CmdletBinding()]
    param (
        [System.Collections.IDictionary] $Dates,
        [System.Collections.IDictionary] $EmailParameters,
        [System.Collections.IDictionary] $FormattingParameters,
        [System.Collections.IDictionary] $ReportOptions,
        [System.Collections.IDictionary] $ReportDefinitions
    )

    $time = [System.Diagnostics.Stopwatch]::StartNew() # Timer Start
    # Declare variables
    $EventLogTable = @()
    $TableEventLogFiles = @()

    $Logger.AddInfoRecord("Processing report for dates from: $($Dates.DateFrom) to $($Dates.DateTo)")
    $Logger.AddInfoRecord('Establishing servers list to process...')

    $ServersAD = Get-DC
    $Servers = Find-ServersAD -ReportDefinitions $ReportDefinitions -DC $ServersAD

    $Logger.AddInfoRecord('Preparing Security Events list to be processed')
    $EventsToProcessSecurity = Find-AllEvents -ReportDefinitions $ReportDefinitions -LogNameSearch 'Security'
    $Logger.AddInfoRecord('Preparing System Events list to be processed')
    $EventsToProcessSystem = Find-AllEvents -ReportDefinitions $ReportDefinitions -LogNameSearch 'System'

    # Summary of events to process
    $Logger.AddInfoRecord("Found security events to process: $($EventsToProcessSecurity -join ', ')")
    $Logger.AddInfoRecord("Found system events to process: $($EventsToProcessSystem -join ', ')")

    $AllErrors = @()

    $Events = New-ArrayList
    if ($ReportDefinitions.ReportsAD.Servers.UseForwarders) {
        $Logger.AddInfoRecord("Preparing Forwarded Events on forwarding servers: $($ReportDefinitions.ReportsAD.Servers.ForwardServer -join ', ')")
        foreach ($ForwardedServer in $ReportDefinitions.ReportsAD.Servers.ForwardServer) {
            #$Events += Get-Events -Server $ReportDefinitions.ReportsAD.ForwardServer -LogName $ReportDefinitions.ReportsAD.ForwardServer.ForwardEventLog
            $FoundEvents = Get-AllRequiredEvents -Servers $ForwardedServer `
                -Dates $Dates `
                -Events $EventsToProcessSecurity `
                -LogName $ReportDefinitions.ReportsAD.Servers.ForwardEventLog `
                -ErrorAction SilentlyContinue `
                -ErrorVariable +AllErrors `
                -Verbose:$ReportOptions.Debug.Verbose
            Add-ToArrayAdvanced -List $Events -Element $FoundEvents -SkipNull -Merge
            $FoundEvents = Get-AllRequiredEvents -Servers $ForwardedServer `
                -Dates $Dates `
                -Events $EventsToProcessSystem `
                -LogName $ReportDefinitions.ReportsAD.Servers.ForwardEventLog `
                -ErrorAction SilentlyContinue `
                -ErrorVariable +AllErrors `
                -Verbose:$ReportOptions.Debug.Verbose
            Add-ToArrayAdvanced -List $Events -Element $FoundEvents -SkipNull -Merge
        }
    }
    if ($ReportDefinitions.ReportsAD.Servers.UseDirectScan) {
        $Logger.AddInfoRecord("Processing Security Events from directly scanned servers: $($Servers -Join ', ')")
        $FoundEvents = Get-AllRequiredEvents -Servers $Servers `
            -Dates $Dates `
            -Events $EventsToProcessSecurity -LogName 'Security' `
            -ErrorAction SilentlyContinue `
            -ErrorVariable +AllErrors `
            -Verbose:$ReportOptions.Debug.Verbose
        Add-ToArrayAdvanced -List $Events -Element $FoundEvents -SkipNull -Merge
        $Logger.AddInfoRecord("Processing System Events from directly scanned servers: $($Servers -Join ', ')")
        $FoundEvents = Get-AllRequiredEvents -Servers $Servers `
            -Dates $Dates `
            -Events $EventsToProcessSystem -LogName 'System' `
            -ErrorAction SilentlyContinue `
            -ErrorVariable +AllErrors `
            -Verbose:$ReportOptions.Debug.Verbose
        Add-ToArrayAdvanced -List $Events -Element $FoundEvents -SkipNull -Merge
    }
    if ($ReportDefinitions.ReportsAD.ArchiveProcessing.Use) {
        $EventLogFiles = Get-EventLogFileList -Sections $ReportDefinitions.ReportsAD.ArchiveProcessing
        foreach ($File in $EventLogFiles) {
            $TableEventLogFiles += Get-FileInformation -File $File
            $Logger.AddInfoRecord("Processing Security Events on file: $File")
            $FoundEvents = Get-AllRequiredEvents `
                -FilePath $File `
                -Dates $Dates `
                -Events $EventsToProcessSecurity `
                -LogName 'Security' `
                -ErrorAction SilentlyContinue `
                -ErrorVariable +AllErrors `
                -Verbose:$ReportOptions.Debug.Verbose
            Add-ToArrayAdvanced -List $Events -Element $FoundEvents -SkipNull -Merge
            $Logger.AddInfoRecord("Processing System Events on file: $File")
            $FoundEvents = Get-AllRequiredEvents `
                -FilePath $File `
                -Dates $Dates `
                -Events $EventsToProcessSystem `
                -LogName 'System' `
                -ErrorAction SilentlyContinue `
                -ErrorVariable +AllErrors `
                -Verbose:$ReportOptions.Debug.Verbose
            Add-ToArrayAdvanced -List $Events -Element $FoundEvents -SkipNull -Merge
        }
    }

    foreach ($Errors in $AllErrors) {
        $Logger.AddErrorRecord($Errors)
    }
    $Logger.AddInfoRecord('Processing Event Log Sizes on defined servers for warnings')
    $EventLogDatesSummary = @()
    if ($ReportDefinitions.ReportsAD.Servers.UseForwarders) {
        $Logger.AddInfoRecord("Processing Event Log Sizes on $($ReportDefinitions.ReportsAD.Servers.ForwardServer) for warnings")
        $EventLogDatesSummary += Get-EventLogSize -Servers $ReportDefinitions.ReportsAD.Servers.ForwardServer -LogName $ReportDefinitions.ReportsAD.Servers.ForwardEventLog -Verbose:$ReportOptions.Debug.Verbose
    }
    if ($ReportDefinitions.ReportsAD.Servers.UseDirectScan) {
        $Logger.AddInfoRecord("Processing Event Log Sizes on $($Servers -Join ', ') for warnings")
        $EventLogDatesSummary += Get-EventLogSize -Servers $Servers -LogName 'Security'
        $EventLogDatesSummary += Get-EventLogSize -Servers $Servers -LogName 'System'
    }
    $Logger.AddInfoRecord('Verifying Warnings reported earlier')
    $Warnings = Invoke-EventLogVerification -Results $EventLogDatesSummary -Dates $Dates

    if ($ReportOptions.RemoveDuplicates.Enabled) {
        $Logger.AddInfoRecord("Removing Duplicates from all events. Current list contains $(Get-ObjectCount -Object $Events) events")
        $Events = Remove-DuplicateObjects -Object $Events -Property $ReportOptions.RemoveDuplicates.Properties
        $Logger.AddInfoRecord("Removed Duplicates Following $(Get-ObjectCount -Object $Events) events will be analyzed further")
    }

    # Process events
    $Results = @{}
    foreach ($ReportName in $ReportDefinitions.ReportsAD.EventBased.Keys) {
        if ($ReportDefinitions.ReportsAD.EventBased.$ReportName.Enabled -eq $true) {
            $Logger.AddInfoRecord("Running $ReportName Report")
            $TimeExecution = Start-TimeLog
            $Results.$ReportName = Get-EventsWorkaround -Events $Events -IgnoreWords $ReportDefinitions.ReportsAD.EventBased.$ReportName.IgnoreWords -Report $ReportName
            $ElapsedTime = Stop-TimeLog -Time $TimeExecution -Option OneLiner
            $Logger.AddInfoRecord("Ending $ReportName Report - Elapsed time: $ElapsedTime")
        }
    }

    if ($ReportDefinitions.ReportsAD.Custom.EventLogSize.Enabled -eq $true) {

        if ($ReportDefinitions.ReportsAD.Servers.UseForwarders) {
            foreach ($LogName in $ReportDefinitions.ReportsAD.Servers.ForwardEventLog) {
                $Logger.AddInfoRecord("Running Event Log Size Report for $LogName log")
                $EventLogTable += Get-EventLogSize -Servers $ReportDefinitions.ReportsAD.Servers.ForwardServer  -LogName $LogName
                $Logger.AddInfoRecord("Ending Event Log Size Report for $LogName log")
            }
        }
        foreach ($LogName in $ReportDefinitions.ReportsAD.Custom.EventLogSize.Logs) {
            $Logger.AddInfoRecord("Running Event Log Size Report for $LogName log")
            $EventLogTable += Get-EventLogSize -Servers $Servers -LogName $LogName
            $Logger.AddInfoRecord("Ending Event Log Size Report for $LogName log")
        }
        if ($ReportDefinitions.ReportsAD.Custom.EventLogSize.SortBy -ne "") { $EventLogTable = $EventLogTable | Sort-Object $ReportDefinitions.ReportsAD.Custom.EventLogSize.SortBy }

    }

    # Prepare email body
    $Logger.AddInfoRecord('Prepare email head and body')
    $HtmlHead = Set-EmailHead -FormattingOptions $FormattingParameters
    $HtmlBody = Set-EmailReportBranding -FormattingParameters $FormattingParameters
    $HtmlBody += Set-EmailReportDetails -FormattingParameters $FormattingParameters -Dates $Dates -Warnings $Warnings

    # prepare body with HTML
    if ($ReportOptions.AsHTML.Use) {
        $HtmlBody += Export-ReportToHTML -Report $ReportDefinitions.ReportsAD.Custom.ServersData.Enabled -ReportTable $ServersAD -ReportTableText 'Following AD servers were detected in forest'
        $HtmlBody += Export-ReportToHTML -Report $ReportDefinitions.ReportsAD.Custom.FilesData.Enabled -ReportTable $TableEventLogFiles -ReportTableText 'Following files have been processed for events'
        $HtmlBody += Export-ReportToHTML -Report $ReportDefinitions.ReportsAD.Custom.EventLogSize.Enabled -ReportTable $EventLogTable -ReportTableText 'Following event log sizes were reported'
        foreach ($ReportName in $ReportDefinitions.ReportsAD.EventBased.Keys) {
            $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName -ToLowerCase
            $HtmlBody += Export-ReportToHTML -Report $ReportDefinitions.ReportsAD.EventBased.$ReportName.Enabled -ReportTable $Results.$ReportName -ReportTableText "Following $ReportNameTitle happened"

        }
    }

    $Reports = @()

    if ($ReportOptions.AsDynamicHTML.Use) {
        $ReportFileName = Set-ReportFile -FileNamePattern $ReportOptions.AsDynamicHTML.FilePattern -DateFormat $ReportOptions.AsDynamicHTML.DateFormat

        $DynamicHTML = New-HTML -TitleText $ReportOptions.AsDynamicHTML.Title `
            -HideLogos:(-not $ReportOptions.AsDynamicHTML.Branding.Logo.Show) `
            -RightLogoString $ReportOptions.AsDynamicHTML.Branding.Logo.RightLogo.ImageLink `
            -UseCssLinks:$ReportOptions.AsDynamicHTML.EmbedCSS `
            -UseStyleLinks:$ReportOptions.AsDynamicHTML.EmbedJS {

            foreach ($ReportName in $ReportDefinitions.ReportsAD.EventBased.Keys) {
                $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName
                if ($ReportDefinitions.ReportsAD.EventBased.$ReportName.Enabled) {
                    New-HTMLContent -HeaderText $ReportNameTitle -CanCollapse {
                        New-HTMLColumn -Columns 1 {
                            if ($null -ne $Results.$ReportName) {
                                Get-HTMLContentDataTable -ArrayOfObjects $Results.$ReportName -HideFooter
                            }
                        }
                    }
                }
            }
        }
        if ($null -ne $DynamicHTML) {
            [string] $DynamicHTMLPath = Save-HTML -HTML $DynamicHTML -FilePath "$($ReportOptions.AsDynamicHTML.Path)\$ReportFileName"
            $Reports += $DynamicHTMLPath
        }
    }

    if ($ReportOptions.AsExcel) {
        $Logger.AddInfoRecord('Prepare XLSX files with Events')
        $ReportFilePathXLSX = Set-ReportFileName -ReportOptions $ReportOptions -ReportExtension "xlsx"
        Export-ReportToXLSX -Report $ReportDefinitions.ReportsAD.Custom.ServersData.Enabled -ReportOptions $ReportOptions -ReportFilePath $ReportFilePathXLSX -ReportName "Processed Servers" -ReportTable $ServersAD
        Export-ReportToXLSX -Report $ReportDefinitions.ReportsAD.Custom.EventLogSize.Enabled -ReportOptions $ReportOptions -ReportFilePath $ReportFilePathXLSX -ReportName "Event log sizes" -ReportTable $EventLogTable

        foreach ($ReportName in $ReportDefinitions.ReportsAD.EventBased.Keys) {
            $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName
            Export-ReportToXLSX -Report $ReportDefinitions.ReportsAD.EventBased.$ReportName.Enabled -ReportOptions $ReportOptions -ReportFilePath $ReportFilePathXLSX -ReportName $ReportNameTitle -ReportTable $Results.$ReportName
        }
        $Reports += $ReportFilePathXLSX
    }
    if ($ReportOptions.AsCSV) {
        $Logger.AddInfoRecord('Prepare CSV files with Events')
        $Reports += Export-ReportToCSV -Report $ReportDefinitions.ReportsAD.Custom.ServersData.Enabled -ReportOptions $ReportOptions -Extension "csv" -ReportName "ReportServers" -ReportTable $ServersAD
        $Reports += Export-ReportToCSV -Report $ReportDefinitions.ReportsAD.Custom.EventLogSize.Enabled -ReportOptions $ReportOptions -Extension "csv" -ReportName "ReportEventLogSize" -ReportTable $EventLogTable

        foreach ($ReportName in $ReportDefinitions.ReportsAD.EventBased.Keys) {
            $Reports += Export-ReportToCSV -Report $ReportDefinitions.ReportsAD.EventBased.$ReportName.Enabled -ReportOptions $ReportOptions -Extension "csv" -ReportName $ReportName -ReportTable $Results.$ReportName
        }
    }
    $Reports = $Reports |  Where-Object { $_ } | Sort-Object -Unique

    $Logger.AddInfoRecord('Prepare Email replacements and formatting')
    # Do Cleanup of Emails

    $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateDays**' -ReplaceWith $time.Elapsed.Days
    $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateHours**' -ReplaceWith $time.Elapsed.Hours
    $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateMinutes**' -ReplaceWith $time.Elapsed.Minutes
    $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateSeconds**' -ReplaceWith $time.Elapsed.Seconds
    $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateMilliseconds**' -ReplaceWith $time.Elapsed.Milliseconds
    $HtmlBody = Set-EmailFormatting -Template $HtmlBody -FormattingParameters $FormattingParameters -ConfigurationParameters $ReportOptions -Logger $Logger -SkipNewLines

    $EmailBody = $HtmlHead + $HtmlBody

    $Time.Stop()

    # Sending email - finalizing package
    if ($ReportOptions.SendMail) {

        foreach ($Report in $Reports) {
            $Logger.AddInfoRecord("Following files will be attached to email $Report")
        }


        $TemporarySubject = $EmailParameters.EmailSubject -replace "<<DateFrom>>", "$($Dates.DateFrom)" -replace "<<DateTo>>", "$($Dates.DateTo)"
        $Logger.AddInfoRecord('Sending email with reports')
        if ($FormattingParameters.CompanyBranding.Inline) {
            $SendMail = Send-Email -EmailParameters $EmailParameters -Body $EmailBody -Attachment $Reports -Subject $TemporarySubject -InlineAttachments @{logo = $FormattingParameters.CompanyBranding.Logo} #-Verbose
        } else {
            $SendMail = Send-Email -EmailParameters $EmailParameters -Body $EmailBody -Attachment $Reports -Subject $TemporarySubject #-Verbose
        }
        if ($SendMail.Status) {
            $Logger.AddInfoRecord('Email successfully sent')
        } else {
            $Logger.AddInfoRecord("Error sending message: $($SendMail.Error)")
        }
    }

    if ($ReportOptions.AsHTML.Use) {
        $ReportHTMLPath = Set-ReportFileName -ReportOptions $ReportOptions -ReportExtension 'html'
        $EmailBody | Out-File -Encoding Unicode -FilePath $ReportHTMLPath
        $Logger.AddInfoRecord("Saving report to file: $ReportHTMLPath")
        if ($ReportHTMLPath -ne '' -and ($ReportOptions.AsHTML.OpenAsFile)) {
            if (Test-Path -LiteralPath $ReportHTMLPath) {
                Invoke-Item $ReportHTMLPath
            }
        }
    }
    if ($ReportOptions.AsDynamicHTML.Use -and $ReportOptions.AsDynamicHTML.OpenAsFile) {
        if ($DynamicHTMLPath -ne '' -and (Test-Path -LiteralPath $DynamicHTMLPath)) {
            Invoke-Item $DynamicHTMLPath
        }
    }

    foreach ($ReportName in $ReportDefinitions.ReportsAD.EventBased.Keys) {
        $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName
        Export-ReportToSql -Report $ReportDefinitions.ReportsAD.EventBased.$ReportName -ReportOptions $ReportOptions -ReportName $ReportNameTitle -ReportTable $Results.$ReportName
    }
    Remove-ReportsFiles -KeepReports $ReportOptions.KeepReports -ReportFiles $Reports
}
function Start-ReportSpecial {
    [CmdletBinding()]
    param (
        [System.Collections.IDictionary] $Dates,
        [alias('ReportOptions')][System.Collections.IDictionary] $Options,
        [alias('ReportDefinitions')][System.Collections.IDictionary] $Definitions,
        [alias('Servers', 'Computers')][System.Collections.IDictionary] $Target
    )
    $Verbose = ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -eq $true)
    $Time = Start-TimeLog
    $Results = @{}
    $AttachedReports = @()
    $AttachXLSX = @()
    $AttachHTML = @()
    $AttachDynamicHTML = @()
    $AttachCSV = @()

    [Array] $ExtendedInput = Get-ServersList -Definitions $Definitions -Target $Target
    foreach ($Entry in $ExtendedInput) {
        if ($Entry.Type -eq 'Computer') {
            $Logger.AddInfoRecord("Computer $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')")
        } else {
            $Logger.AddInfoRecord("File $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')")
        }
    }
    # Scan all events and get everything at once
    $AllEvents = Get-Events `
        -ExtendedInput $ExtendedInput `
        -ErrorAction SilentlyContinue `
        -ErrorVariable AllErrors `
        -Verbose:$Verbose

    $Logger.AddInfoRecord("Found $($AllEvents.Count) events.")
    foreach ($Errors in $AllErrors) {
        $Logger.AddErrorRecord($Errors)
    }

    if ($Options.RemoveDuplicates.Enabled) {
        $Logger.AddInfoRecord("Removing Duplicates from all events. Current list contains $(Get-ObjectCount -Object $AllEvents) events")
        $AllEvents = Remove-DuplicateObjects -Object $AllEvents -Property $Options.RemoveDuplicates.Properties
        $Logger.AddInfoRecord("Removed Duplicates Following $(Get-ObjectCount -Object $AllEvents) events will be analyzed further")
    }


    # Prepare the results based on chosen criteria
    foreach ($Report in  $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled' }) {
        if ($Definitions.$Report.Enabled) {
            #$ReportNameTitle = Format-AddSpaceToSentence -Text $Report -ToLowerCase
            $Logger.AddInfoRecord("Running $Report")
            $TimeExecution = Start-TimeLog
            foreach ($SubReport in $Definitions.$Report.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport'  }) {
                if ($Definitions.$Report.$SubReport.Enabled) {
                    $Logger.AddInfoRecord("Running $Report with subsection $SubReport")
                    [string] $EventsType = $Definitions.$Report.$SubReport.LogName
                    [Array] $EventsNeeded = $Definitions.$Report.$SubReport.Events
                    [Array] $EventsFound = Find-EventsNeeded -Events $AllEvents -EventIDs $EventsNeeded -EventsType $EventsType
                    [Array] $EventsFound = Get-EventsTranslation -Events $EventsFound -EventsDefinition $Definitions.$Report.$SubReport
                    $Logger.AddInfoRecord("Ending $Report with subsection $SubReport events found $($EventsFound.Count)")
                    $Results.$Report = $EventsFound
                }
            }
            $ElapsedTimeReport = Stop-TimeLog -Time $TimeExecution -Option OneLiner
            $Logger.AddInfoRecord("Ending $Report - Time to run $ElapsedTimeReport")
        }
    }

    # Prepare email body - tables (real data)
    if ($Options.AsHTML.Enabled) {
        # Prepare email body
        $Logger.AddInfoRecord('Prepare email head and body')
        $HtmlHead = Set-EmailHead -FormattingOptions $Options.AsHTML.Formatting
        $HtmlBody = Set-EmailReportBranding -FormattingParameters $Options.AsHTML.Formatting
        $HtmlBody += Set-EmailReportDetails -FormattingParameters $Options.AsHTML.Formatting -Dates $Dates -Warnings $Warnings

        #$EmailBody += Export-ReportToHTML -Report $Definitions.ReportsAD.Custom.ServersData.Enabled -ReportTable $ServersAD -ReportTableText 'Following AD servers were detected in forest'
        #$EmailBody += Export-ReportToHTML -Report $Definitions.ReportsAD.Custom.FilesData.Enabled -ReportTable $TableEventLogFiles -ReportTableText 'Following files have been processed for events'
        #$EmailBody += Export-ReportToHTML -Report $Definitions.ReportsAD.Custom.EventLogSize.Enabled -ReportTable $EventLogTable -ReportTableText 'Following event log sizes were reported'
        foreach ($ReportName in $Definitions.Keys) {
            $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName -ToLowerCase
            $HtmlBody += Export-ReportToHTML -Report $Definitions.$ReportName.Enabled -ReportTable $Results.$ReportName -ReportTableText "Following $ReportNameTitle happened"

        }
        # Do Cleanup of Emails
        $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateDays**' -ReplaceWith $time.Elapsed.Days
        $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateHours**' -ReplaceWith $time.Elapsed.Hours
        $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateMinutes**' -ReplaceWith $time.Elapsed.Minutes
        $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateSeconds**' -ReplaceWith $time.Elapsed.Seconds
        $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateMilliseconds**' -ReplaceWith $time.Elapsed.Milliseconds
        $HtmlBody = Set-EmailFormatting -Template $HtmlBody -FormattingParameters $Options.AsHTML.Formatting -ConfigurationParameters $Options -Logger $Logger -SkipNewLines

        $HTML = $HtmlHead + $HtmlBody
        #$ReportHTMLPath = Set-ReportFileName -ReportOptions $Options -ReportExtension 'html'
        $ReportHTMLPath = Set-ReportFile -Path $Options.AsHTML.Path -FileNamePattern $Options.AsHTML.FilePattern -DateFormat $Options.AsHTML.DateFormat
        try {
            $HTML | Out-File -Encoding Unicode -FilePath $ReportHTMLPath -ErrorAction Stop
            $Logger.AddInfoRecord("Saving report to file: $ReportHTMLPath")
            if ($Options.SendMail.Attach.HTML) {
                $AttachHTML += $ReportHTMLPath
                $AttachedReports += $ReportHTMLPath
            }
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            $Logger.AddErrorRecord("Error saving file $ReportHTMLPath.")
            $Logger.AddErrorRecord("Error: $ErrorMessage")
        }
    }


    if ($Options.AsDynamicHTML.Enabled) {
        $ReportFileName = Set-ReportFile -Path $Options.AsDynamicHTML.Path -FileNamePattern $Options.AsDynamicHTML.FilePattern -DateFormat $Options.AsDynamicHTML.DateFormat

        $DynamicHTML = New-HTML -TitleText $Options.AsDynamicHTML.Title `
            -HideLogos:(-not $Options.AsDynamicHTML.Branding.Logo.Show) `
            -RightLogoString $Options.AsDynamicHTML.Branding.Logo.RightLogo.ImageLink `
            -UseCssLinks:$Options.AsDynamicHTML.EmbedCSS `
            -UseStyleLinks:$Options.AsDynamicHTML.EmbedJS {

            foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) {
                $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName
                if ($Definitions.$ReportName.Enabled) {
                    New-HTMLContent -HeaderText $ReportNameTitle -CanCollapse {
                        New-HTMLColumn -Columns 1 {
                            if ($null -ne $Results.$ReportName) {
                                New-HTMLTable -DataTable $Results.$ReportName -HideFooter
                            }
                        }
                    }
                }

            }
        }
        if ($null -ne $DynamicHTML) {
            try {
                [string] $DynamicHTMLPath = Save-HTML -HTML $DynamicHTML -FilePath $ReportFileName
                if ($Options.SendMail.Attach.DynamicHTML) {
                    $AttachDynamicHTML += $DynamicHTMLPath
                    $AttachedReports += $DynamicHTMLPath
                }
            } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                $Logger.AddErrorRecord("Error saving file $ReportHTMLPath.")
                $Logger.AddErrorRecord("Error: $ErrorMessage")
            }
        }
    }
    if ($Options.AsExcel.Enabled) {
        $Logger.AddInfoRecord('Prepare Microsoft Excel (.XLSX) file with Events')
        $ReportFilePathXLSX = Set-ReportFile -Path $Options.AsExcel.Path -FileNamePattern $Options.AsExcel.FilePattern -DateFormat $Options.AsExcel.DateFormat
        # $ReportFilePathXLSX = Set-ReportFileName -ReportOptions $Options -ReportExtension "xlsx"
        #Export-ReportToXLSX -Report $Definitions.ReportsAD.Custom.ServersData.Enabled -ReportOptions $Options -ReportFilePath $ReportFilePathXLSX -ReportName "Processed Servers" -ReportTable $ServersAD
        #Export-ReportToXLSX -Report $Definitions.ReportsAD.Custom.EventLogSize.Enabled -ReportOptions $Options -ReportFilePath $ReportFilePathXLSX -ReportName "Event log sizes" -ReportTable $EventLogTable

        foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) {
            $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName
            Export-ReportToXLSX -Report $Definitions.$ReportName.Enabled -ReportOptions $Options -ReportFilePath $ReportFilePathXLSX -ReportName $ReportNameTitle -ReportTable $Results.$ReportName
        }
        if ($Options.SendMail.Attach.XLSX) {
            $AttachXLSX += $ReportFilePathXLSX
            $AttachedReports += $ReportFilePathXLSX
        }
    }
    if ($Options.AsCSV.Enabled) {
        $ReportFilePathCSV = @()
        $Logger.AddInfoRecord('Prepare CSV files with Events')
        #$ReportFilePathCSV += Export-ReportToCSV -Report $Definitions.ReportsAD.Custom.ServersData.Enabled -ReportOptions $Options -Extension "csv" -ReportName "ReportServers" -ReportTable $ServersAD
        #$ReportFilePathCSV += Export-ReportToCSV -Report $Definitions.ReportsAD.Custom.EventLogSize.Enabled -ReportOptions $Options -Extension "csv" -ReportName "ReportEventLogSize" -ReportTable $EventLogTable

        foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) {
            $ReportFilePathCSV += Export-ToCSV -Report $Definitions.$ReportName.Enabled -ReportName $ReportName -ReportTable $Results.$ReportName -Path $Options.AsCSV.Path -FilePattern $Options.AsCSV.FilePattern -DateFormat $Options.AsCSV.DateFormat
        }
        if ($Options.SendMail.Attach.CSV) {
            $AttachCSV += $ReportFilePathCSV
            $AttachedReports += $ReportFilePathCSV
        }
    }
    if ($Options.AsHTML.Enabled -and $Options.AsHTML.OpenAsFile) {
        if ($ReportHTMLPath -ne '' -and (Test-Path -LiteralPath $ReportHTMLPath)) {
            Invoke-Item -LiteralPath $ReportHTMLPath
        }
    }
    if ($Options.AsDynamicHTML.Enabled -and $Options.AsDynamicHTML.OpenAsFile) {
        if ($DynamicHTMLPath -ne '' -and (Test-Path -LiteralPath $DynamicHTMLPath)) {
            Invoke-Item -LiteralPath $DynamicHTMLPath
        }
    }
    if ($Options.AsExcel.Enabled -and $Options.AsExcel.OpenAsFile) {
        if ($ReportFilePathXLSX -ne '' -and (Test-Path -LiteralPath $ReportFilePathXLSX)) {
            Invoke-Item -LiteralPath $ReportFilePathXLSX
        }
    }
    if ($Options.AsCSV.Enabled -and $Options.AsCSV.OpenAsFile) {
        foreach ($CSV in $AttachCSV) {
            if ($CSV -ne '' -and (Test-Path -LiteralPath $CSV)) {
                Invoke-Item -LiteralPath $CSV
            }
        }
    }

    $AttachedReports = $AttachedReports |  Where-Object { $_ } | Sort-Object -Unique


    # Sending email - finalizing package
    if ($Options.SendMail.Enabled) {
        foreach ($Report in $AttachedReports) {
            $Logger.AddInfoRecord("Following files will be attached to email $Report")
        }
        if ($Options.SendMail.InlineHTML) {
            $EmailBody = $HTML
        } else {
            $EmailBody = ''
        }

        $TemporarySubject = $Options.SendMail.Parameters.Subject -replace "<<DateFrom>>", "$($Dates.DateFrom)" -replace "<<DateTo>>", "$($Dates.DateTo)"
        $Logger.AddInfoRecord('Sending email with reports')
        if ($Options.AsHTML.Formatting.CompanyBranding.Inline) {
            $SendMail = Send-Email -EmailParameters $Options.SendMail.Parameters -Body $EmailBody -Attachment $AttachedReports -Subject $TemporarySubject -InlineAttachments @{logo = $Options.AsHTML.Formatting.CompanyBranding.Logo} -Logger $Logger
        } else {
            $SendMail = Send-Email -EmailParameters $Options.SendMail.Parameters -Body $EmailBody -Attachment $AttachedReports -Subject $TemporarySubject -Logger $Logger
        }
        if ($SendMail.Status) {
            $Logger.AddInfoRecord('Email successfully sent')
        } else {
            $Logger.AddInfoRecord("Error sending message: $($SendMail.Error)")
        }
        Remove-ReportsFiles -KeepReports $Options.SendMail.KeepReports.XLSX -ReportFiles $AttachXLSX
        Remove-ReportsFiles -KeepReports $Options.SendMail.KeepReports.CSV -ReportFiles $AttachCSV
        Remove-ReportsFiles -KeepReports $Options.SendMail.KeepReports.HTML -ReportFiles $AttachHTML
        Remove-ReportsFiles -KeepReports $Options.SendMail.KeepReports.DynamicHTML -ReportFiles $AttachDynamicHTML
    }
    foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) {
        $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName
        Export-ToSql -Report $Definitions.$ReportName -ReportOptions $Options -ReportName $ReportNameTitle -ReportTable $Results.$ReportName
    }

    $ElapsedTime = Stop-TimeLog -Time $Time
    $Logger.AddInfoRecord("Time to finish $ElapsedTime")
}
function Start-SubscriptionService {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $LoggerParameters
    )
    if (-not $LoggerParameters) {
        $LoggerParameters = $Script:LoggerParameters
    }
    $Logger = Get-Logger @LoggerParameters
    $Logger.AddInfoRecord('Starting Windows Event Collector service.')
    #Write-Color 'Starting Windows Event Collector service.' -Color White, Green, White
    $Output = Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'qc', '/q:true'
    $Logger.AddInfoRecord($Output)
}
function Start-WinReporting {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)][System.Collections.IDictionary]$Times,
        [Parameter(Mandatory = $true)][alias('ReportOptions')][System.Collections.IDictionary] $Options,
        [Parameter(Mandatory = $true)][alias('ReportDefinitions')][System.Collections.IDictionary] $Definitions,
        [Parameter(Mandatory = $true)][alias('Servers', 'Computers')][System.Collections.IDictionary] $Target
    )
    # Logger Setup
    if ($Options.Logging) {
        $LoggerParameters = $Options.Logging
    } else {
        $LoggerParameters = $Script:LoggerParameters
    }
    $Logger = Get-Logger @LoggerParameters
    # Test Configuration
    #TODO


    # Test Modules
    #TODO

    # Run report
    $Dates = Get-ChoosenDates -ReportTimes $Times
    foreach ($Date in $Dates) {
        $Logger.AddInfoRecord("Starting to build a report for dates $($Date.DateFrom) to $($Date.DateTo)")
        Start-ReportSpecial `
            -Dates $Date `
            -Options $Options `
            -Definitions $Definitions `
            -Target $Target
    }
}
function Test-Configuration () {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$LoggerParameters,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$EmailParameters,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$FormattingParameters,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$ReportOptions,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$ReportTimes,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$ReportDefinitions
    )
    $Logger.AddInfoRecord('Testing for configuration consistency. This is to make sure the script can be safely executed.')
    # Configuration successful check flag
    $Success = $true

    #region EmailParameters
    $Success = (Test-Key $LoggerParameters "LoggerParameters" "ShowTime" -DisplayProgress) -and $Success
    $Success = (Test-Key $LoggerParameters "LoggerParameters" "LogsDir" -DisplayProgress) -and $Success
    $Success = (Test-Key $LoggerParameters "LoggerParameters" "TimeFormat" -DisplayProgress) -and $Success
    #endregion EmailParameters

    #region EmailParameters
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailFrom" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailTo" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailCC" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailBCC" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailServer" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailServerPassword" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailServerPort" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailServerLogin" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailServerEnableSSL" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailEncoding" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailSubject" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailPriority" -DisplayProgress) -and $Success
    $Success = (Test-Key $EmailParameters "EmailParameters" "EmailReplyTo" -DisplayProgress) -and $Success
    #endregion EmailParameters

    #region FormattingParameters
    $Success = (Test-Key $FormattingParameters "FormattingParameters" "CompanyBranding" -DisplayProgress) -and $Success
    if (Test-Key $FormattingParameters "FormattingParameters" "CompanyBranding" ) {
        $Success = (Test-Key $FormattingParameters.CompanyBranding "FormattingParameters.CompanyBranding" "Logo" -DisplayProgress) -and $Success
        $Success = (Test-Key $FormattingParameters.CompanyBranding "FormattingParameters.CompanyBranding" "Inline" -DisplayProgress) -and $Success
        $Success = (Test-Key $FormattingParameters.CompanyBranding "FormattingParameters.CompanyBranding" "Width" -DisplayProgress) -and $Success
        $Success = (Test-Key $FormattingParameters.CompanyBranding "FormattingParameters.CompanyBranding" "Height" -DisplayProgress) -and $Success
        $Success = (Test-Key $FormattingParameters.CompanyBranding "FormattingParameters.CompanyBranding" "Link" -DisplayProgress) -and $Success
    }
    $Success = (Test-Key $FormattingParameters "FormattingParameters" "FontFamily" -DisplayProgress) -and $Success
    $Success = (Test-Key $FormattingParameters "FormattingParameters" "FontSize" -DisplayProgress) -and $Success
    $Success = (Test-Key $FormattingParameters "FormattingParameters" "FontHeadingFamily" -DisplayProgress) -and $Success
    $Success = (Test-Key $FormattingParameters "FormattingParameters" "FontHeadingSize" -DisplayProgress) -and $Success
    #endregion FormattingParameters

    #region ReportOptions
    $Success = (Test-Key $ReportOptions "ReportOptions" "JustTestPrerequisite" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportOptions "ReportOptions" "AsExcel" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportOptions "ReportOptions" "AsCSV" -DisplayProgress) -and $Success
    #$Success = (Test-Key $ReportOptions "ReportOptions" "AsHTML" -DisplayProgress) -and $Success

    $Success = (Test-Key $ReportOptions "ReportOptions" "AsHTML" -DisplayProgress) -and $Success
    if (Test-Key $ReportOptions "ReportOptions" "AsHTML" ) {
        $Success = (Test-Key $ReportOptions.AsHTML "ReportOptions.AsHTML" "Use" -DisplayProgress -ValueType 'Boolean') -and $Success
        $Success = (Test-Key $ReportOptions.AsHTML "ReportOptions.ASHTML" "OpenAsFile" -DisplayProgress -ValueType 'Boolean') -and $Success
    }

    $Success = (Test-Key $ReportOptions "ReportOptions" "AsDynamicHTML" -DisplayProgress) -and $Success
    if (Test-Key $ReportOptions "ReportOptions" "AsDynamicHTML" ) {
        $Success = (Test-Key $ReportOptions.AsDynamicHTML "ReportOptions.AsDynamicHTML" "Use" -DisplayProgress -ValueType 'Boolean') -and $Success
        $Success = (Test-Key $ReportOptions.AsDynamicHTML "ReportOptions.AsDynamicHTML" "OpenAsFile" -DisplayProgress -ValueType 'Boolean') -and $Success
        $Success = (Test-Key $ReportOptions.AsDynamicHTML "ReportOptions.AsDynamicHTML" "Title" -DisplayProgress -ValueType 'string') -and $Success
        $Success = (Test-Key $ReportOptions.AsDynamicHTML "ReportOptions.AsDynamicHTML" "Path" -DisplayProgress -ValueType 'string') -and $Success
        $Success = (Test-Key $ReportOptions.AsDynamicHTML "ReportOptions.AsDynamicHTML" "FilePattern" -DisplayProgress -ValueType 'string') -and $Success
        $Success = (Test-Key $ReportOptions.AsDynamicHTML "ReportOptions.AsDynamicHTML" "DateFormat" -DisplayProgress -ValueType 'string') -and $Success
        $Success = (Test-Key $ReportOptions.AsDynamicHTML "ReportOptions.AsDynamicHTML" "EmbedCSS" -DisplayProgress -ValueType 'Boolean') -and $Success
        $Success = (Test-Key $ReportOptions.AsDynamicHTML "ReportOptions.AsDynamicHTML" "EmbedJS" -DisplayProgress -ValueType 'Boolean') -and $Success

    }





    $Success = (Test-Key $ReportOptions "ReportOptions" "SendMail" -DisplayProgress) -and $Success

    $Success = (Test-Key $ReportOptions "ReportOptions" "KeepReports" -DisplayProgress) -and $Success
    if (Test-Key $ReportOptions "ReportOptions" "KeepReports" ) {
        if (-not (Test-Path $ReportOptions.KeepReportsPath -PathType Container)) {
            $Success = $false
            $Logger.AddErrorRecord('Path in configuration of ReportOptions.KeepReportsPath doesn''t exist.')
        }
    }
    $Success = (Test-Key $ReportOptions "ReportOptions" "FilePattern" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportOptions "ReportOptions" "FilePatternDateFormat" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportOptions "ReportOptions" "RemoveDuplicates" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportOptions "ReportOptions" "Debug" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportOptions.Debug "ReportOptions.Debug" "DisplayTemplateHTML" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportOptions.Debug "ReportOptions.Debug" "Verbose" -DisplayProgress) -and $Success
    if ($ReportOptions.Contains("AsSql")) {
        $Success = (Test-Key $ReportOptions.AsSql "ReportOptions.AsSql" "Use" -DisplayProgress) -and $Success
        if ($ReportOptions.AsSql.Contains("Use") -and $ReportOptions.AsSql.Use) {
            $Success = (Test-Key $ReportOptions.AsSql "ReportOptions.AsSql" "SqlServer" -DisplayProgress) -and $Success
            $Success = (Test-Key $ReportOptions.AsSql "ReportOptions.AsSql" "SqlDatabase" -DisplayProgress) -and $Success
            $Success = (Test-Key $ReportOptions.AsSql "ReportOptions.AsSql" "SqlTable" -DisplayProgress) -and $Success
            $Success = (Test-Key $ReportOptions.AsSql "ReportOptions.AsSql" "SqlTableCreate" -DisplayProgress) -and $Success
            $Success = (Test-Key $ReportOptions.AsSql "ReportOptions.AsSql" "SqlTableAlterIfNeeded" -DisplayProgress) -and $Success
            $Success = (Test-Key $ReportOptions.AsSql "ReportOptions.AsSql" "SqlCheckBeforeInsert" -DisplayProgress) -and $Success
            <# This is not required to exists. Only if SQL is needed, used.
            $Success = (Test-Key $ReportOptions.AsSql "ReportOptions.AsSql" "SqlTableMapping" -DisplayProgress) -and $Success
            if (Test-Key $ReportOptions.AsSql "ReportOptions.AsSql" "SqlTableMapping" ) {
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Event ID" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Who" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "When" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Record ID" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Domain Controller" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Action" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Group Name" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "User Affected" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Member Name" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Computer Lockout On" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Reported By" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "SamAccountName" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Display Name" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "UserPrincipalName" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Home Directory" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Home Path" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Script Path" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Profile Path" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "User Workstation" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Password Last Set" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Account Expires" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Primary Group Id" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Allowed To Delegate To" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Old Uac Value" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "New Uac Value" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "User Account Control" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "User Parameters" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Sid History" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Logon Hours" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "OperationType" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Message" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Backup Path" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Log Type" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "AddedWhen" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "AddedWho" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Gathered From" -DisplayProgress) -and $Success
                $Success = (Test-Key $ReportOptions.AsSql.SqlTableMapping "ReportOptions.SqlTableMapping" "Gathered LogName" -DisplayProgress) -and $Success
            }
            #>

        }
    }
    #endregion ReportOptions

    #region Report Definions
    $Success = (Test-Key $ReportDefinitions "ReportDefinitions" "ReportsAD" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD "ReportDefinitions.ReportsAD" "Servers" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.Servers "ReportDefinitions.ReportsAD.Servers" "UseForwarders" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.Servers "ReportDefinitions.ReportsAD.Servers" "ForwardServer" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.Servers "ReportDefinitions.ReportsAD.Servers" "ForwardEventLog" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.Servers "ReportDefinitions.ReportsAD.Servers" "UseDirectScan" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.Servers "ReportDefinitions.ReportsAD.Servers" "Automatic" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.Servers "ReportDefinitions.ReportsAD.Servers" "OnlyPDC" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.Servers "ReportDefinitions.ReportsAD.Servers" "DC" -DisplayProgress) -and $Success
    if (Test-Key $ReportDefinitions.ReportsAD "ReportDefinitions.ReportsAD" "ArchiveProcessing") {
        if (Test-Key $ReportDefinitions.ReportsAD.ArchiveProcessing "ReportDefinitions.ReportsAD.ArchiveProcessing" "Directories" -DisplayProgress) {
            foreach ($Folder in $ReportDefinitions.ReportsAD.ArchiveProcessing.Directories.Values) {
                if (-not (Test-Path $Folder -PathType Container)) {
                    $Success = $false
                    $Logger.AddErrorRecord('Path in configuration of ReportDefinitions.ReportsAD.ArchiveProcessing.Directories doesn''t exist.')
                }
            }
        }
        if (Test-Key $ReportDefinitions.ReportsAD.ArchiveProcessing "ReportDefinitions.ReportsAD.ArchiveProcessing" "Files" -DisplayProgress) {
            foreach ($File in $ReportDefinitions.ReportsAD.ArchiveProcessing.Files.Values) {
                if (-not (Test-Path $File -PathType Leaf)) {
                    $Success = $false
                    $Logger.AddErrorRecord('Path in configuration of ReportDefinitions.ReportsAD.ArchiveProcessing.Files doesn''t exist.')
                }
            }
        }
    }
    $Success = (Test-Key $ReportDefinitions.ReportsAD "ReportDefinitions.ReportsAD" "EventBased" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.EventBased "ReportDefinitions.ReportsAD.EventBased" "UserChanges" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.EventBased.UserChanges "ReportDefinitions.ReportsAD.EventBased.UserChanges" "Enabled" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.EventBased.UserChanges "ReportDefinitions.ReportsAD.EventBased.UserChanges" "Events" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.EventBased.UserChanges "ReportDefinitions.ReportsAD.EventBased.UserChanges" "LogName" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.EventBased.UserChanges "ReportDefinitions.ReportsAD.EventBased.UserChanges" "IgnoreWords" -DisplayProgress) -and $Success

    $Success = (Test-Key $ReportDefinitions.ReportsAD "ReportDefinitions.ReportsAD" "Custom" -DisplayProgress) -and $Success
    $Success = (Test-Key $ReportDefinitions.ReportsAD.Custom "ReportDefinitions.ReportsAD.Custom" "EventLogSize" -DisplayProgress) -and $Success
    if (Test-Key $ReportDefinitions.ReportsAD.Custom "ReportDefinitions.ReportsAD.Custom" "EventLogSize" ) {
        $Success = (Test-Key $ReportDefinitions.ReportsAD.Custom.EventLogSize "ReportDefinitions.ReportsAD.Custom.EventLogSize" "Enabled" -DisplayProgress) -and $Success
        $Success = (Test-Key $ReportDefinitions.ReportsAD.Custom.EventLogSize "ReportDefinitions.ReportsAD.Custom.EventLogSize" "Logs" -DisplayProgress) -and $Success
        $Success = (Test-Key $ReportDefinitions.ReportsAD.Custom.EventLogSize "ReportDefinitions.ReportsAD.Custom.EventLogSize" "SortBy" -DisplayProgress) -and $Success
    }
    $Success = (Test-Key $ReportDefinitions.ReportsAD.Custom "ReportDefinitions.ReportsAD.Custom" "ServersData" -DisplayProgress) -and $Success
    if (Test-Key $ReportDefinitions.ReportsAD.Custom "ReportDefinitions.ReportsAD.Custom" "ServersData" ) {
        $Success = (Test-Key $ReportDefinitions.ReportsAD.Custom.ServersData "ReportDefinitions.ReportsAD.Custom.ServersData" "Enabled" -DisplayProgress) -and $Success
    }
    $Success = (Test-Key $ReportDefinitions.ReportsAD.Custom "ReportDefinitions.ReportsAD.Custom" "FilesData" -DisplayProgress) -and $Success
    if (Test-Key $ReportDefinitions.ReportsAD.Custom "ReportDefinitions.ReportsAD.Custom" "FilesData" ) {
        $Success = (Test-Key $ReportDefinitions.ReportsAD.Custom.ServersData "ReportDefinitions.ReportsAD.Custom.FilesData" "Enabled" -DisplayProgress) -and $Success
    }
    #endregion Report Definions

    #region ReportOptions Per Hour
    $Success = (Test-Key $ReportTimes "ReportTimes" "PastHour" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "PastHour" ) {
        $Success = (Test-Key $ReportTimes.PastHour "ReportTimes.PastHour" "Enabled" -DisplayProgress -ValueType 'Boolean') -and $Success
    }
    $Success = (Test-Key $ReportTimes "ReportTimes" "CurrentHour" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "CurrentHour" ) {
        $Success = (Test-Key $ReportTimes.CurrentHour "ReportTimes.CurrentHour" "Enabled" -DisplayProgress -ValueType 'Boolean') -and $Success
    }
    #endregion ReportTimes Per Hour

    #region ReportTimes Per Day
    $Success = (Test-Key $ReportTimes "ReportTimes" "PastDay" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "PastDay" ) {
        $Success = (Test-Key $ReportTimes.PastDay "ReportTimes.PastDay" "Enabled" -DisplayProgress -ValueType 'Boolean') -and $Success
    }
    $Success = (Test-Key $ReportTimes "ReportTimes" "CurrentDay" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "CurrentDay" ) {
        $Success = (Test-Key $ReportTimes.CurrentDay "ReportTimes.CurrentDay" "Enabled" -DisplayProgress -ValueType 'Boolean') -and $Success
    }
    $Success = (Test-Key $ReportTimes "ReportTimes" "OnDay" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "OnDay" ) {
        $Success = (Test-Key $ReportTimes.OnDay "ReportTimes.OnDay" "Enabled" -DisplayProgress) -and $Success
        $Success = (Test-Key $ReportTimes.OnDay "ReportTimes.OnDay" "Days" -DisplayProgress) -and $Success
    }
    #endregion ReportTimes Per Day

    #region ReportTimes Per Month
    $Success = (Test-Key $ReportTimes "ReportTimes" "PastMonth" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "PastMonth" ) {
        $Success = (Test-Key $ReportTimes.PastMonth "ReportTimes.PastMonth" "Enabled" -DisplayProgress) -and $Success
        $Success = (Test-Key $ReportTimes.PastMonth "ReportTimes.PastMonth" "Force" -DisplayProgress) -and $Success
    }
    $Success = (Test-Key $ReportTimes "ReportTimes" "CurrentMonth" -DisplayProgress) -and $Success
    #endregion ReportTimes Per Month

    #region ReportTimes Per Quarter
    $Success = (Test-Key $ReportTimes "ReportTimes" "PastQuarter" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "PastQuarter" ) {
        $Success = (Test-Key $ReportTimes.PastQuarter "ReportTimes.PastQuarter" "Enabled" -DisplayProgress) -and $Success
        $Success = (Test-Key $ReportTimes.PastQuarter "ReportTimes.PastQuarter" "Force" -DisplayProgress) -and $Success
    }
    $Success = (Test-Key $ReportTimes "ReportTimes" "CurrentQuarter" -DisplayProgress) -and $Success
    #endregion ReportTimes Per Quarter

    #region ReportTimes Custom Dates
    $Success = (Test-Key $ReportTimes "ReportTimes" "CurrentDayMinusDayX" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "CurrentDayMinusDayX" ) {
        $Success = (Test-Key $ReportTimes.CurrentDayMinusDayX "ReportTimes.CurrentDayMinusDayX" "Enabled" -DisplayProgress) -and $Success
        $Success = (Test-Key $ReportTimes.CurrentDayMinusDayX "ReportTimes.CurrentDayMinusDayX" "Days" -DisplayProgress) -and $Success
    }
    $Success = (Test-Key $ReportTimes "ReportTimes" "CurrentDayMinuxDaysX" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "CurrentDayMinuxDaysX" ) {
        $Success = (Test-Key $ReportTimes.CurrentDayMinuxDaysX "ReportTimes.CurrentDayMinuxDaysX" "Enabled" -DisplayProgress) -and $Success
        $Success = (Test-Key $ReportTimes.CurrentDayMinuxDaysX "ReportTimes.CurrentDayMinuxDaysX" "Days" -DisplayProgress) -and $Success
    }
    $Success = (Test-Key $ReportTimes "ReportTimes" "CustomDate" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "CustomDate" ) {
        $Success = (Test-Key $ReportTimes.CustomDate "ReportTimes.CustomDate" "Enabled" -DisplayProgress) -and $Success
        $Success = (Test-Key $ReportTimes.CustomDate "ReportTimes.CustomDate" "DateFrom" -DisplayProgress) -and $Success
        $Success = (Test-Key $ReportTimes.CustomDate "ReportTimes.CustomDate" "DateTo" -DisplayProgress) -and $Success
    }
    $Success = (Test-Key $ReportTimes "ReportTimes" "Everything" -DisplayProgress) -and $Success
    if (Test-Key $ReportTimes "ReportTimes" "Everything" ) {
        $Success = (Test-Key $ReportTimes.PastDay "ReportTimes.Everything" "Enabled" -DisplayProgress -ValueType 'Boolean') -and $Success
    }
    #endregion ReportTimes Custom Dates

    #region ReportOptions Options
    #$Success = (Test-Key $ReportOptions "ReportOptions" "AsExcel" -DisplayProgress) -and $Success
    #$Success = (Test-Key $ReportOptions "ReportOptions" "AsCSV" -DisplayProgress) -and $Success
    #$Success = (Test-Key $ReportOptions "ReportOptions" "AsHTML" -DisplayProgress) -and $Success
    #$Success = (Test-Key $ReportOptions "ReportOptions" "SendMail" -DisplayProgress) -and $Success
    #$Success = (Test-Key $ReportOptions "ReportOptions" "KeepReportsPath" -DisplayProgress) -and $Success
    #$Success = (Test-Key $ReportOptions "ReportOptions" "FilePattern" -DisplayProgress) -and $Success
    #$Success = (Test-Key $ReportOptions "ReportOptions" "FilePatternDateFormat" -DisplayProgress) -and $Success
    #$Success = (Test-Key $ReportOptions "ReportOptions" "RemoveDuplicates" -DisplayProgress) -and $Success
    #endregion ReportOptions Options

    return $Success
}
function Test-Key () {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Object] $ConfigurationTable,
        [Parameter(Mandatory = $true)]
        [string] $ConfigurationSection,
        [Parameter(Mandatory = $true)]
        [string] $ConfigurationKey,
        [string] $ValueType,
        [switch] $DisplayProgress
    )
    Write-Verbose "Test-Key ConfigurationKey: $ConfigurationKey, ConfigurationSection: $ConfigurationSection, ValueType: $ValueType, DisplayProgress: $DisplayProgress"
    if ($ConfigurationTable -is [System.Collections.IDictionary]) {
        if (-not $ConfigurationTable.Contains($ConfigurationKey)) {
            if ($DisplayProgress) {
                $Logger.AddErrorRecord("Parameter $ConfigurationKey in configuration of $ConfigurationSection doesn't exists.")
            }
            return $false
        }
    }
    #Write-Verbose "Test-Key - Continuing 1"
    if (-not $PSBoundParameters.ContainsKey('ValueType')) {
        if ($DisplayProgress) {
            $Logger.AddSuccessRecord("Parameter $ConfigurationKey in configuration of $ConfigurationSection exists")
        }
        return $true
    }
    #Write-Verbose "Test-Key - Continuing 2"
    if ($null -ne $ConfigurationTable.$ConfigurationKey) {
        if (-not ($ConfigurationTable.$ConfigurationKey.GetType().Name -eq $ValueType)) {
            if ($DisplayProgress) {
                $Logger.AddErrorRecord("Parameter $ConfigurationKey in configuration of $ConfigurationSection exists but the type of key is incorrect")
            }
            return $false
        }
    } else {
        if ($DisplayProgress) {
            $Logger.AddErrorRecord("Parameter $ConfigurationKey in configuration of $ConfigurationSection doesn't exists.")
        }
        return $false
    }
    #Write-Verbose "Test-Key - Continuing 3"
    if ($DisplayProgress) {
        $Logger.AddSuccessRecord("Parameter $ConfigurationKey in configuration of $ConfigurationSection exists and correct")
    }
    return $true

}
function Test-Modules () {
    [CmdletBinding()]
    param (
        [System.Collections.IDictionary] $ReportOptions
    )
    $Logger.AddInfoRecord('Testing for prerequisite availability')
    $ImportPSEventViewer = Get-ModulesAvailability -Name "PSEventViewer"
    if ($ImportPSEventViewer -eq $true) {
        $Logger.AddSuccessRecord('PSEventViewer module imported')
    } else {
        $Logger.AddErrorRecord('PSEventViewer module not found')
    }

    $ImportPSADReporting = Get-ModulesAvailability -Name "PSWinReporting"
    if ($ImportPSADReporting) {
        $Logger.AddSuccessRecord('PSWinReporting module imported')
    } else {
        $Logger.AddErrorRecord('PSWinReporting module not found')
    }

    $ImportExcel = Get-ModulesAvailability -Name "PSWriteExcel"
    if ($ImportExcel) {
        $Logger.AddSuccessRecord('PSWriteExcel module imported')
    } else {
        $Logger.AddInfoRecord('PSWriteExcel module not found')
        if ($ReportOptions.AsExcel) {
            $Logger.AddErrorRecord('PSWriteExcel module is not installed. Disable AsExcel under ReportOptions option before rerunning this script')
            $Logger.AddInfoRecord('Alternatively run Install-Module -Name PSWriteExcel before re-running this script. It''s quite useful module!')
            $Logger.AddInfoRecord('If Install-Module is not there as well you need to download PackageManagement PowerShell Modules')
            $Logger.AddInfoRecord('It can be found at https://www.microsoft.com/en-us/download/details.aspx?id=51451. After download, install and re-run Install-Module again.')
        }
    }
    $ImportActiveDirectory = Get-ModulesAvailability -Name "ActiveDirectory"
    if ($ImportActiveDirectory) {
        $Logger.AddSuccessRecord('ActiveDirectory module imported')
    } else {
        $Logger.AddErrorRecord('ActiveDirectory module not found')
        $Logger.AddInfoRecord('Please make sure it''s available on the machine before running this script')
    }

    return ($ImportPSEventViewer -and $ImportPSADReporting -and $ImportActiveDirectory -and (($ReportOptions.AsExcel -and $ImportExcel) -or (-not $ReportOptions.AsExcel)))
}


Export-ModuleMember `
    -Function @('Add-TaskScheduledForwarder','Find-Events','New-SubscriptionTemplates','Remove-TaskScheduledForwarder','Set-SubscriptionTemplates','Start-ADReporting','Start-Notifications','Start-SubscriptionService','Start-WinReporting') `
    -Alias @()