PSWinReportingV2.psm1

function ConvertFrom-Color {
    [alias('Convert-FromColor')]
    [CmdletBinding()]
    param ([ValidateScript( { if ($($_ -in $Script:RGBColors.Keys -or $_ -match "^#([A-Fa-f0-9]{6})$" -or $_ -eq "") -eq $false) { throw "The Input value is not a valid colorname nor an valid color hex code." } else { $true } })]
        [alias('Colors')][string[]] $Color,
        [switch] $AsDecimal)
    $Colors = foreach ($C in $Color) {
        $Value = $Script:RGBColors."$C"
        if ($C -match "^#([A-Fa-f0-9]{6})$") { return $C }
        if ($null -eq $Value) { return }
        $HexValue = Convert-Color -RGB $Value
        Write-Verbose "Convert-FromColor - Color Name: $C Value: $Value HexValue: $HexValue"
        if ($AsDecimal) { [Convert]::ToInt64($HexValue, 16) } else { "#$($HexValue)" }
    }
    $Colors
}
function ConvertFrom-OperationType {
    param ([string] $OperationType)
    $Known = @{'%%14674' = 'Value Added'
        '%%14675'        = 'Value Deleted'
        '%%14676'        = 'Unknown'
    }
    foreach ($id in $OperationType) { if ($name = $Known[$id]) { return $name } }
    return $OperationType
}
function Convert-UAC {
    <#
    .SYNOPSIS
        Converts values from Events into proper format
 
    .DESCRIPTION
        Converts values from Events into proper format
 
    .PARAMETER UAC
        Parameter description
 
    .EXAMPLE
        Convert-UAC -UAC '%%1793'
        Convert-UAC -UAC '1793'
        Output: TEMP_DUPLICATE_ACCOUNT, NORMAL_ACCOUNT, RESERVED
 
        Convert-UAC -UAC '1793', '1794'
 
        Convert-UAC -UAC '121793'
        Output: PASSWD_CANT_CHANGE, ENCRYPTED_TEXT_PWD_ALLOWED, TEMP_DUPLICATE_ACCOUNT, NORMAL_ACCOUNT, INTERDOMAIN_TRUST_ACCOUNT, WORKSTATION_TRUST_ACCOUNT, RESERVED, RESERVED, DONT_EXPIRE_PASSWORD
 
        Convert-UAC -UAC 'C:\Onet33'
        Output: Same input as output
 
        Convert-UAC -UAC '121793' -OutputPerLine
        Output: One entry per line
            PASSWD_CANT_CHANGE
            ENCRYPTED_TEXT_PWD_ALLOWED
            TEMP_DUPLICATE_ACCOUNT
            NORMAL_ACCOUNT
            INTERDOMAIN_TRUST_ACCOUNT
            WORKSTATION_TRUST_ACCOUNT
            RESERVED
            RESERVED
            DONT_EXPIRE_PASSWORD
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string[]] $UAC,
        [string] $Separator)
    $Output = foreach ($String in $UAC) {
        $NumberAsString = $String.Replace('%', '') -as [int]
        if ($null -eq $NumberAsString) { return $UAC }
        $PropertyFlags = @("SCRIPT",
            "ACCOUNTDISABLE",
            "RESERVED",
            "HOMEDIR_REQUIRED",
            "LOCKOUT",
            "PASSWD_NOTREQD",
            "PASSWD_CANT_CHANGE",
            "ENCRYPTED_TEXT_PWD_ALLOWED",
            "TEMP_DUPLICATE_ACCOUNT",
            "NORMAL_ACCOUNT",
            "RESERVED",
            "INTERDOMAIN_TRUST_ACCOUNT",
            "WORKSTATION_TRUST_ACCOUNT",
            "SERVER_TRUST_ACCOUNT",
            "RESERVED",
            "RESERVED",
            "DONT_EXPIRE_PASSWORD",
            "MNS_LOGON_ACCOUNT",
            "SMARTCARD_REQUIRED",
            "TRUSTED_FOR_DELEGATION",
            "NOT_DELEGATED",
            "USE_DES_KEY_ONLY",
            "DONT_REQ_PREAUTH",
            "PASSWORD_EXPIRED",
            "TRUSTED_TO_AUTH_FOR_DELEGATION",
            "RESERVED",
            "PARTIAL_SECRETS_ACCOUNT"
            "RESERVED"
            "RESERVED"
            "RESERVED"
            "RESERVED"
            "RESERVED")
        1..($PropertyFlags.Length) | Where-Object { $NumberAsString -bAnd [math]::Pow(2, $_) } | ForEach-Object { $PropertyFlags[$_] }
    }
    if ($Separator -eq '') { $Output } else { $Output -join $Separator }
}
function Find-DatesCurrentDayMinusDayX {
    param($days)
    $DateTodayStart = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(- $Days)
    $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(1).AddDays(- $Days).AddMilliseconds(-1)
    $DateParameters = @{DateFrom = $DateTodayStart
        DateTo                   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesCurrentDayMinuxDaysX {
    param($days)
    $DateTodayStart = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(- $Days)
    $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(1).AddMilliseconds(-1)
    $DateParameters = @{DateFrom = $DateTodayStart
        DateTo                   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesCurrentHour {
    $DateTodayStart = (Get-Date -Minute 0 -Second 0 -Millisecond 0)
    $DateTodayEnd = $DateTodayStart.AddHours(1)
    $DateParameters = @{DateFrom = $DateTodayStart
        DateTo                   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesDayPrevious {
    $DateToday = (Get-Date).Date
    $DateYesterday = $DateToday.AddDays(-1)
    $DateParameters = @{DateFrom = $DateYesterday
        DateTo                   = $dateToday
    }
    return $DateParameters
}
function Find-DatesDayToday {
    $DateToday = (Get-Date).Date
    $DateTodayEnd = $DateToday.AddDays(1).AddSeconds(-1)
    $DateParameters = @{DateFrom = $DateToday
        DateTo                   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesMonthCurrent {
    $DateMonthFirstDay = (Get-Date -Day 1).Date
    $DateMonthLastDay = Get-Date $DateMonthFirstDay.AddMonths(1).AddSeconds(-1)
    $DateParameters = @{DateFrom = $DateMonthFirstDay
        DateTo                   = $DateMonthLastDay
    }
    return $DateParameters
}
function Find-DatesMonthPast {
    param([bool] $Force)
    $DateToday = (Get-Date).Date
    $DateMonthFirstDay = (Get-Date -Day 1).Date
    $DateMonthPreviousFirstDay = $DateMonthFirstDay.AddMonths(-1)
    if ($Force -eq $true -or $DateToday -eq $DateMonthFirstDay) {
        $DateParameters = @{DateFrom = $DateMonthPreviousFirstDay
            DateTo                   = $DateMonthFirstDay
        }
        return $DateParameters
    } else { return $null }
}
function Find-DatesPastHour {
    $DateTodayEnd = Get-Date -Minute 0 -Second 0 -Millisecond 0
    $DateTodayStart = $DateTodayEnd.AddHours(-1)
    $DateParameters = @{DateFrom = $DateTodayStart
        DateTo                   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesPastWeek {
    param($DayName)
    $DateTodayStart = Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0
    if ($DateTodayStart.DayOfWeek -ne $DayName) { return $null }
    $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(-7)
    $DateParameters = @{DateFrom = $DateTodayEnd
        DateTo                   = $DateTodayStart
    }
    return $DateParameters
}
function Find-DatesQuarterCurrent {
    param([bool] $Force)
    $Today = (Get-Date)
    $Quarter = [Math]::Ceiling($Today.Month / 3)
    $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3))
    $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date
    $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1)
    $DateParameters = @{DateFrom = $StartDate
        DateTo                   = $EndDate
    }
    return $DateParameters
}
function Find-DatesQuarterLast {
    param([bool] $Force)
    $Today = (Get-Date).AddDays(-90)
    $Yesterday = ((Get-Date).AddDays(-1))
    $Quarter = [Math]::Ceiling($Today.Month / 3)
    $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3))
    $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date
    $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1)
    if ($Force -eq $true -or $Yesterday.Date -eq $EndDate.Date) {
        $DateParameters = @{DateFrom = $StartDate
            DateTo                   = $EndDate
        }
        return $DateParameters
    } else { return $null }
}
function Format-AddSpaceToSentence {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Text
    Parameter description
 
    .EXAMPLE
 
 
    $test = @(
        'OnceUponATime',
        'OnceUponATime1',
        'Money@Risk',
        'OnceUponATime123',
        'AHappyMan2014'
        'OnceUponATime_123'
    )
 
    Format-AddSpaceToSentence -Text $Test
 
    $Test | Format-AddSpaceToSentence -ToLowerCase
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)][string[]] $Text,
        [switch] $ToLowerCase)
    Begin { }
    Process {
        $Value = foreach ($T in $Text) { ($T -creplace '([A-Z\W_]|\d+)(?<![a-z])', ' $&').trim() }
        if ($ToLowerCase) { $Value.ToLower() } else { $Value }
    }
    End { }
}
function Format-FirstXChars {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Text
    Parameter description
 
    .PARAMETER NumberChars
    Parameter description
 
    .EXAMPLE
    Format-FirstChars -Text "VERBOSE: Loading module from path 'C:\Users\pklys\.vscode\extensions\ms-vs" -NumberChars 15
 
    .NOTES
    General notes
    #>

    param([string] $Text,
        [int] $NumberChars)
    return ($Text.ToCharArray() | Select-Object -First $NumberChars) -join ''
}
function Get-FilesInFolder {
    [CmdletBinding()]
    param([string] $Folder,
        [string] $Extension = '*.evtx')
    $Files = Get-ChildItem -Path $Folder -Filter $Extension -Recurse
    $ReturnFiles = foreach ($File in $Files) { $File.FullName }
    return $ReturnFiles
}
function Get-Logger {
    [CmdletBinding(DefaultParameterSetName = "All")]
    param ([Parameter(Mandatory = $false, ParameterSetName = 'Logpath')][string] $LogPath,
        [Parameter(Mandatory = $false, ParameterSetName = 'Complexpath')][string] $LogsDir,
        [Parameter(Mandatory = $false, ParameterSetName = 'Complexpath')][string] $Filename,
        [switch] $ShowTime,
        [string] $TimeFormat = 'yyyy-MM-dd HH:mm:ss')
    if ($PSCmdlet.ParameterSetName -eq 'Complexpath') {
        if (-not $Filename) {
            $CallerName = [System.IO.Path]::GetFileNameWithoutExtension((Split-Path $MyInvocation.PSCommandPath -Leaf))
            $Filename = "$([DateTime]::Now.ToString($TimeFormat) -replace('[^.\-\w]', '_'))_$CallerName.log"
        }
        $LogPath = Join-Path $LogsDir $Filename
    }
    if ($LogPath) {
        $LogsDir = [System.IO.Path]::GetDirectoryName($LogPath)
        New-Item $LogsDir -ItemType Directory -Force | Out-Null
        New-Item $LogPath -ItemType File -Force | Out-Null
    }
    $Logger = [PSCustomObject]@{LogPath = $LogPath
        ShowTime                        = $ShowTime
        TimeFormat                      = $TimeFormat
    }
    Add-Member -InputObject $Logger -MemberType ScriptMethod AddErrorRecord -Value { param([Parameter(Mandatory = $true)]
            [string]$String)
        if (-not $this.LogPath) { Write-Color -Text "[Error] ", $String -Color Red, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Error] ", $String -Color Red, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } }
    Add-Member -InputObject $Logger -MemberType ScriptMethod AddInfoRecord -Value { param([Parameter(Mandatory = $true)]
            [string]$String)
        if (-not $this.LogPath) { Write-Color -Text "[Info] ", $String -Color Yellow, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Info] ", $String -Color Yellow, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } }
    Add-Member -InputObject $Logger -MemberType ScriptMethod AddWarningRecord -Value { param([Parameter(Mandatory = $true)]
            [string]$String)
        if (-not $this.LogPath) { Write-Color -Text "[Warning] ", $String -Color Magenta, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Warning] ", $String -Color Magenta, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } }
    Add-Member -InputObject $Logger -MemberType ScriptMethod AddRecord -Value { param([Parameter(Mandatory = $true)]
            [string]$String)
        if (-not $this.LogPath) { Write-Color -Text " $String" -Color White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text " $String" -Color White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } }
    Add-Member -InputObject $Logger -MemberType ScriptMethod AddSuccessRecord -Value { param([Parameter(Mandatory = $true)]
            [string]$String)
        if (-not $this.LogPath) { Write-Color -Text "[Success] ", $String -Color Green, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Success] ", $String -Color Green, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } }
    return $Logger
}
function Get-WinADForestControllers {
    [alias('Get-WinADDomainControllers')]
    <#
    .SYNOPSIS
 
 
    .DESCRIPTION
    Long description
 
    .PARAMETER TestAvailability
    Parameter description
 
    .EXAMPLE
    Get-WinADForestControllers -TestAvailability | Format-Table
 
    .EXAMPLE
    Get-WinADDomainControllers
 
    .EXAMPLE
    Get-WinADDomainControllers | Format-Table *
 
    Output:
 
    Domain HostName Forest IPV4Address IsGlobalCatalog IsReadOnly SchemaMaster DomainNamingMasterMaster PDCEmulator RIDMaster InfrastructureMaster Comment
    ------ -------- ------ ----------- --------------- ---------- ------------ ------------------------ ----------- --------- -------------------- -------
    ad.evotec.xyz AD1.ad.evotec.xyz ad.evotec.xyz 192.168.240.189 True False True True True True True
    ad.evotec.xyz AD2.ad.evotec.xyz ad.evotec.xyz 192.168.240.192 True False False False False False False
    ad.evotec.pl ad.evotec.xyz False False False False False Unable to contact the server. This may be becau...
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string[]] $Domain,
        [switch] $TestAvailability,
        [switch] $SkipEmpty)
    try {
        $Forest = Get-ADForest
        if (-not $Domain) { $Domain = $Forest.Domains }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        Write-Warning "Get-WinADForestControllers - Couldn't use Get-ADForest feature. Error: $ErrorMessage"
        return
    }
    $Servers = foreach ($D in $Domain) {
        try {
            $LocalServer = Get-ADDomainController -Discover -DomainName $D -ErrorAction Stop
            $DC = Get-ADDomainController -Server $LocalServer.HostName[0] -Filter * -ErrorAction Stop
            foreach ($S in $DC) {
                $Server = [ordered] @{Domain = $D
                    HostName                 = $S.HostName
                    Name                     = $S.Name
                    Forest                   = $Forest.RootDomain
                    IPV4Address              = $S.IPV4Address
                    IPV6Address              = $S.IPV6Address
                    IsGlobalCatalog          = $S.IsGlobalCatalog
                    IsReadOnly               = $S.IsReadOnly
                    Site                     = $S.Site
                    SchemaMaster             = ($S.OperationMasterRoles -contains 'SchemaMaster')
                    DomainNamingMaster       = ($S.OperationMasterRoles -contains 'DomainNamingMaster')
                    PDCEmulator              = ($S.OperationMasterRoles -contains 'PDCEmulator')
                    RIDMaster                = ($S.OperationMasterRoles -contains 'RIDMaster')
                    InfrastructureMaster     = ($S.OperationMasterRoles -contains 'InfrastructureMaster')
                    LdapPort                 = $S.LdapPort
                    SslPort                  = $S.SslPort
                    Pingable                 = $null
                    Comment                  = ''
                }
                if ($TestAvailability) { $Server['Pingable'] = foreach ($_ in $Server.IPV4Address) { Test-Connection -Count 1 -Server $_ -Quiet -ErrorAction SilentlyContinue } }
                [PSCustomObject] $Server
            }
        } catch {
            [PSCustomObject]@{Domain     = $D
                HostName                 = ''
                Name                     = ''
                Forest                   = $Forest.RootDomain
                IPV4Address              = ''
                IPV6Address              = ''
                IsGlobalCatalog          = ''
                IsReadOnly               = ''
                Site                     = ''
                SchemaMaster             = $false
                DomainNamingMasterMaster = $false
                PDCEmulator              = $false
                RIDMaster                = $false
                InfrastructureMaster     = $false
                LdapPort                 = ''
                SslPort                  = ''
                Pingable                 = $null
                Comment                  = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            }
        }
    }
    if ($SkipEmpty) { return $Servers | Where-Object { $_.HostName -ne '' } }
    return $Servers
}
function Get-WinADForestDetails {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [string] $Filter = '*',
        [switch] $TestAvailability,
        [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All',
        [int[]] $Ports = 135,
        [int] $PortsTimeout = 100,
        [int] $PingCount = 1)
    if ($Global:ProgressPreference -ne 'SilentlyContinue') {
        $TemporaryProgress = $Global:ProgressPreference
        $Global:ProgressPreference = 'SilentlyContinue'
    }
    $Findings = [ordered] @{ }
    try { if ($Forest) { $ForestInformation = Get-ADForest -ErrorAction Stop -Identity $Forest } else { $ForestInformation = Get-ADForest -ErrorAction Stop } } catch {
        Write-Warning "Get-WinADForestDetails - Error discovering DC for Forest - $($_.Exception.Message)"
        return
    }
    if (-not $ForestInformation) { return }
    $Findings['Forest'] = $ForestInformation
    $Findings['ForestDomainControllers'] = @()
    $Findings['QueryServers'] = @{ }
    $Findings['QueryServers']['Forest'] = $DC
    $Findings.Domains = foreach ($_ in $ForestInformation.Domains) {
        if ($IncludeDomains) {
            if ($_ -in $IncludeDomains) { $_.ToLower() }
            continue
        }
        if ($_ -notin $ExcludeDomains) { $_.ToLower() }
    }
    $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) {
        try { $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop } catch {
            Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)"
            continue
        }
        $Findings['QueryServers']["$Domain"] = $DC
        [Array] $AllDC = try {
            try { $DomainControllers = Get-ADDomainController -Filter $Filter -Server $DC.HostName[0] -ErrorAction Stop } catch {
                Write-Warning "Get-WinADForestDetails - Error listing DCs for domain $Domain - $($_.Exception.Message)"
                continue
            }
            foreach ($S in $DomainControllers) {
                if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } }
                if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } }
                $Server = [ordered] @{Domain = $Domain
                    HostName                 = $S.HostName
                    Name                     = $S.Name
                    Forest                   = $ForestInformation.RootDomain
                    Site                     = $S.Site
                    IPV4Address              = $S.IPV4Address
                    IPV6Address              = $S.IPV6Address
                    IsGlobalCatalog          = $S.IsGlobalCatalog
                    IsReadOnly               = $S.IsReadOnly
                    IsSchemaMaster           = ($S.OperationMasterRoles -contains 'SchemaMaster')
                    IsDomainNamingMaster     = ($S.OperationMasterRoles -contains 'DomainNamingMaster')
                    IsPDC                    = ($S.OperationMasterRoles -contains 'PDCEmulator')
                    IsRIDMaster              = ($S.OperationMasterRoles -contains 'RIDMaster')
                    IsInfrastructureMaster   = ($S.OperationMasterRoles -contains 'InfrastructureMaster')
                    OperatingSystem          = $S.OperatingSystem
                    OperatingSystemVersion   = $S.OperatingSystemVersion
                    OperatingSystemLong      = ConvertTo-OperatingSystem -OperatingSystem $S.OperatingSystem -OperatingSystemVersion $S.OperatingSystemVersion
                    LdapPort                 = $S.LdapPort
                    SslPort                  = $S.SslPort
                    DistinguishedName        = $S.ComputerObjectDN
                    Pingable                 = $null
                    WinRM                    = $null
                    PortOpen                 = $null
                    Comment                  = ''
                }
                if ($TestAvailability) {
                    if ($Test -eq 'All' -or $Test -like 'Ping*') { $Server.Pingable = Test-Connection -ComputerName $Server.IPV4Address -Quiet -Count $PingCount }
                    if ($Test -eq 'All' -or $Test -like '*WinRM*') { $Server.WinRM = (Test-WinRM -ComputerName $Server.HostName).Status }
                    if ($Test -eq 'All' -or '*PortOpen*') { $Server.PortOpen = (Test-ComputerPort -Server $Server.HostName -PortTCP $Ports -Timeout $PortsTimeout).Status }
                }
                [PSCustomObject] $Server
            }
        } catch {
            [PSCustomObject]@{Domain     = $Domain
                HostName                 = ''
                Name                     = ''
                Forest                   = $ForestInformation.RootDomain
                IPV4Address              = ''
                IPV6Address              = ''
                IsGlobalCatalog          = ''
                IsReadOnly               = ''
                Site                     = ''
                SchemaMaster             = $false
                DomainNamingMasterMaster = $false
                PDCEmulator              = $false
                RIDMaster                = $false
                InfrastructureMaster     = $false
                LdapPort                 = ''
                SslPort                  = ''
                DistinguishedName        = ''
                Pingable                 = $null
                WinRM                    = $null
                PortOpen                 = $null
                Comment                  = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            }
        }
        if ($SkipRODC) { $Findings[$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { $Findings[$Domain] = $AllDC }
        $Findings[$Domain]
    }
    if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress }
    $Findings
}
function Remove-DuplicateObjects {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Object
    Parameter description
 
    .PARAMETER Property
    Parameter description
 
    .EXAMPLE
    $Array = @()
    $Array += [PSCustomObject] @{ 'Name' = 'Test'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'}
    $Array += [PSCustomObject] @{ 'Name' = 'Test'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'}
    $Array += [PSCustomObject] @{ 'Name' = 'Test1'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'}
    $Array += [PSCustomObject] @{ 'Name' = 'Test1'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'}
 
    Write-Color 'Before' -Color Red
    $Array | Format-Table -A
 
    Write-Color 'After' -Color Green
    $Array = $Array | Sort-Object -Unique -Property 'Name', 'Val1','Val2'
    $Array | Format-Table -AutoSize
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([System.Collections.IList] $Object,
        [string[]] $Property)
    if ($Object.Count -eq 0) { return $Object } else { return $Object | Sort-Object -Property $Property -Unique }
}
function Remove-WhiteSpace {
    param([string] $Text)
    $Text = $Text -replace '(^\s+|\s+$)', '' -replace '\s+', ' '
    return $Text
}
function Save-XML {
    param ([string] $FilePath,
        [System.Xml.XmlNode] $xml)
    $utf8WithoutBom = New-Object System.Text.UTF8Encoding($false)
    $writer = New-Object System.IO.StreamWriter($FilePath, $false, $utf8WithoutBom)
    $xml.Save($writer)
    $writer.Close()
}
function Send-Email {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param ([alias('EmailParameters')][System.Collections.IDictionary] $Email,
        [string] $Body,
        [string[]] $Attachment,
        [System.Collections.IDictionary] $InlineAttachments,
        [string] $Subject,
        [string[]] $To,
        [PSCustomObject] $Logger)
    try {
        if ($Email.EmailTo) { $EmailParameters = $Email.Clone() } else {
            $EmailParameters = @{EmailFrom  = $Email.From
                EmailTo                     = $Email.To
                EmailCC                     = $Email.CC
                EmailBCC                    = $Email.BCC
                EmailReplyTo                = $Email.ReplyTo
                EmailServer                 = $Email.Server
                EmailServerPassword         = $Email.Password
                EmailServerPasswordAsSecure = $Email.PasswordAsSecure
                EmailServerPasswordFromFile = $Email.PasswordFromFile
                EmailServerPort             = $Email.Port
                EmailServerLogin            = $Email.Login
                EmailServerEnableSSL        = $Email.EnableSsl
                EmailEncoding               = $Email.Encoding
                EmailEncodingSubject        = $Email.EncodingSubject
                EmailEncodingBody           = $Email.EncodingBody
                EmailSubject                = $Email.Subject
                EmailPriority               = $Email.Priority
                EmailDeliveryNotifications  = $Email.DeliveryNotifications
                EmailUseDefaultCredentials  = $Email.UseDefaultCredentials
            }
        }
    } catch {
        return @{Status = $False
            Error       = $($_.Exception.Message)
            SentTo      = ''
        }
    }
    $SmtpClient = [System.Net.Mail.SmtpClient]::new()
    if ($EmailParameters.EmailServer) { $SmtpClient.Host = $EmailParameters.EmailServer } else {
        return @{Status = $False
            Error       = "Email Server Host is not set."
            SentTo      = ''
        }
    }
    if ($EmailParameters.EmailServerPort) { $SmtpClient.Port = $EmailParameters.EmailServerPort } else {
        return @{Status = $False
            Error       = "Email Server Port is not set."
            SentTo      = ''
        }
    }
    if ($EmailParameters.EmailServerLogin) {
        $Credentials = Request-Credentials -UserName $EmailParameters.EmailServerLogin -Password $EmailParameters.EmailServerPassword -AsSecure:$EmailParameters.EmailServerPasswordAsSecure -FromFile:$EmailParameters.EmailServerPasswordFromFile -NetworkCredentials
        $SmtpClient.Credentials = $Credentials
    }
    if ($EmailParameters.EmailServerEnableSSL) { $SmtpClient.EnableSsl = $EmailParameters.EmailServerEnableSSL }
    $MailMessage = [System.Net.Mail.MailMessage]::new()
    $MailMessage.From = $EmailParameters.EmailFrom
    if ($To) { foreach ($T in $To) { $MailMessage.To.add($($T)) } } else { if ($EmailParameters.Emailto) { foreach ($To in $EmailParameters.Emailto) { $MailMessage.To.add($($To)) } } }
    if ($EmailParameters.EmailCC) { foreach ($CC in $EmailParameters.EmailCC) { $MailMessage.CC.add($($CC)) } }
    if ($EmailParameters.EmailBCC) { foreach ($BCC in $EmailParameters.EmailBCC) { $MailMessage.BCC.add($($BCC)) } }
    if ($EmailParameters.EmailReplyTo) { $MailMessage.ReplyTo = $EmailParameters.EmailReplyTo }
    $MailMessage.IsBodyHtml = $true
    if ($Subject -eq '') { $MailMessage.Subject = $EmailParameters.EmailSubject } else { $MailMessage.Subject = $Subject }
    $MailMessage.Priority = [System.Net.Mail.MailPriority]::$($EmailParameters.EmailPriority)
    if ($EmailParameters.EmailEncodingSubject) { $MailMessage.SubjectEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncodingSubject) } else { $MailMessage.SubjectEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncoding) }
    if ($EmailParameters.EmailEncodingBody) { $MailMessage.BodyEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncodingBody) } else { $MailMessage.BodyEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncoding) }
    if ($EmailParameters.EmailUseDefaultCredentials) { $SmtpClient.UseDefaultCredentials = $EmailParameters.EmailUseDefaultCredentials }
    if ($EmailParameters.EmailDeliveryNotifications) { $MailMessage.DeliveryNotificationOptions = $EmailParameters.EmailDeliveryNotifications }
    if ($PSBoundParameters.ContainsKey('InlineAttachments')) {
        $BodyPart = [Net.Mail.AlternateView]::CreateAlternateViewFromString($Body, 'text/html')
        $MailMessage.AlternateViews.Add($BodyPart)
        foreach ($Entry in $InlineAttachments.GetEnumerator()) {
            try {
                $FilePath = $Entry.Value
                Write-Verbose $FilePath
                if ($Entry.Value.StartsWith('http')) {
                    $FileName = $Entry.Value.Substring($Entry.Value.LastIndexOf("/") + 1)
                    $FilePath = Join-Path $env:temp $FileName
                    Invoke-WebRequest -Uri $Entry.Value -OutFile $FilePath
                }
                $ContentType = Get-MimeType -FileName $FilePath
                $InAttachment = [Net.Mail.LinkedResource]::new($FilePath, $ContentType)
                $InAttachment.ContentId = $Entry.Key
                $BodyPart.LinkedResources.Add($InAttachment)
            } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                Write-Error "Error inlining attachments: $ErrorMessage"
            }
        }
    } else { $MailMessage.Body = $Body }
    if ($PSBoundParameters.ContainsKey('Attachment')) {
        foreach ($Attach in $Attachment) {
            if (Test-Path -LiteralPath $Attach) {
                try {
                    $File = [Net.Mail.Attachment]::new($Attach)
                    $MailMessage.Attachments.Add($File)
                } catch {
                    $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                    if ($Logger) { $Logger.AddErrorRecord("Error attaching file $Attach`: $ErrorMessage") } else { Write-Error "Error attaching file $Attach`: $ErrorMessage" }
                }
            }
        }
    }
    try {
        $MailSentTo = "$($MailMessage.To) $($MailMessage.CC) $($MailMessage.BCC)".Trim()
        if ($pscmdlet.ShouldProcess("$MailSentTo", "Send-Email")) {
            $SmtpClient.Send($MailMessage)
            $MailMessage.Dispose()
            return @{Status = $True
                Error       = ""
                SentTo      = $MailSentTo
            }
        }
    } catch {
        $MailMessage.Dispose()
        return @{Status = $False
            Error       = $($_.Exception.Message)
            SentTo      = ""
        }
    }
}
function Send-SqlInsert {
    [CmdletBinding()]
    param([Array] $Object,
        [System.Collections.IDictionary] $SqlSettings)
    if ($SqlSettings.SqlTableTranspose) { $Object = Format-TransposeTable -Object $Object }
    $SqlTable = Get-SqlQueryColumnInformation -SqlServer $SqlSettings.SqlServer -SqlDatabase $SqlSettings.SqlDatabase -Table $SqlSettings.SqlTable
    $PropertiesFromAllObject = Get-ObjectPropertiesAdvanced -Object $Object -AddProperties 'AddedWhen', 'AddedWho'
    $PropertiesFromTable = $SqlTable.Column_name
    if ($null -eq $SqlTable) {
        if ($SqlSettings.SqlTableCreate) {
            Write-Verbose "Send-SqlInsert - SqlTable doesn't exists, table creation is allowed, mapping will be done either on properties from object or from TableMapping defined in config"
            $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject
            $CreateTableSQL = New-SqlQueryCreateTable -SqlSettings $SqlSettings -TableMapping $TableMapping
        } else {
            Write-Verbose "Send-SqlInsert - SqlTable doesn't exists, no table creation is allowed. Terminating"
            return "Error occured: SQL Table doesn't exists. SqlTableCreate option is disabled"
        }
    } else {
        if ($SqlSettings.SqlTableAlterIfNeeded) {
            if ($SqlSettings.SqlTableMapping) {
                Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is allowed, but SqlTableMapping is already defined"
                $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject
            } else {
                Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is allowed, and SqlTableMapping is not defined"
                $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject
                $AlterTableSQL = New-SqlQueryAlterTable -SqlSettings $SqlSettings -TableMapping $TableMapping -ExistingColumns $SqlTable.Column_name
            }
        } else {
            if ($SqlSettings.SqlTableMapping) {
                Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is not allowed, SqlTableMaping is already defined"
                $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject
            } else {
                Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is not allowed, SqlTableMaping is not defined, using SqlTable Columns"
                $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromTable -BasedOnSqlTable
            }
        }
    }
    $Queries = @(if ($CreateTableSQL) { foreach ($Sql in $CreateTableSQL) { $Sql } }
        if ($AlterTableSQL) { foreach ($Sql in $AlterTableSQL) { $Sql } }
        $SqlQueries = New-SqlQuery -Object $Object -SqlSettings $SqlSettings -TableMapping $TableMapping
        foreach ($Sql in $SqlQueries) { $Sql })
    $ReturnData = foreach ($Query in $Queries) {
        try {
            if ($Query) {
                $Query
                Invoke-DbaQuery -SqlInstance "$($SqlSettings.SqlServer)" -Database "$($SqlSettings.SqlDatabase)" -Query $Query -ErrorAction Stop
            }
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            "Error occured (Send-SqlInsert): $ErrorMessage"
        }
    }
    return $ReturnData
}
function Set-EmailBody {
    [CmdletBinding()]
    param([Object] $TableData,
        [alias('TableWelcomeMessage')][string] $TableMessageWelcome,
        [string] $TableMessageNoData = 'No changes happened during that period.')
    $Body = "<p><i><u>$TableMessageWelcome</u></i></p>"
    if ($($TableData | Measure-Object).Count -gt 0) { $Body += $TableData | ConvertTo-Html -Fragment | Out-String } else { $Body += "<p><i>$TableMessageNoData</i></p>" }
    return $body
}
function Set-EmailBodyPreparedTable {
    param($TableData, $TableWelcomeMessage)
    $body = "<p><i><u>$TableWelcomeMessage</u></i></p>"
    $body += $TableData
    return $body
}
function Set-EmailFormatting {
    [CmdletBinding()]
    param ($Template,
        [System.Collections.IDictionary] $FormattingParameters,
        [System.Collections.IDictionary] $ConfigurationParameters,
        [PSCustomObject] $Logger,
        [switch] $SkipNewLines,
        [string[]] $AddAfterOpening,
        [string[]] $AddBeforeClosing,
        [string] $Image)
    if ($ConfigurationParameters) { $WriteParameters = $ConfigurationParameters.DisplayConsole } else { $WriteParameters = @{ShowTime = $true; LogFile = ""; TimeFormat = "yyyy-MM-dd HH:mm:ss" } }
    if ($Image) { $Template = $Template -replace '<<Image>>', $Image }
    $Body = "<body>"
    if ($AddAfterOpening) { $Body += $AddAfterOpening }
    if (-not $SkipNewLines) {
        $Template = $Template.Split("`n")
        if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding HTML <BR> tags...") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "<BR>", " tags." -Color White, Yellow, White, Yellow }
        foreach ($t in $Template) { $Body += "$t<br>" }
    } else { $Body += $Template }
    foreach ($style in $FormattingParameters.Styles.GetEnumerator()) {
        foreach ($value in $style.Value) {
            if ($value -eq "") { continue }
            if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding HTML $($style.Name) tag for $value.") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "$($style.Name)", " tag for ", "$value", ' tags...' -Color White, Yellow, White, Yellow, White, Yellow }
            $Body = $Body.Replace($value, "<$($style.Name)>$value</$($style.Name)>")
        }
    }
    foreach ($color in $FormattingParameters.Colors.GetEnumerator()) {
        foreach ($value in $color.Value) {
            if ($value -eq "") { continue }
            if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding HTML $($color.Name) tag for $value.") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "$($color.Name)", " tag for ", "$value", ' tags...' -Color White, Yellow, White, Yellow, White, Yellow }
            $Body = $Body.Replace($value, "<span style=color:$($color.Name)>$value</span>")
        }
    }
    foreach ($links in $FormattingParameters.Links.GetEnumerator()) {
        foreach ($link in $links.Value) {
            if ($link.Link -like "*@*") {
                if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding EMAIL Links for $($links.Key).") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " EMAIL ", "Links for", " $($links.Key)..." -Color White, Yellow, White, White, Yellow, White }
                $Body = $Body -replace "<<$($links.Key)>>", "<span style=color:$($link.Color)><a href='mailto:$($link.Link)?subject=$($Link.Subject)'>$($Link.Text)</a></span>"
            } else {
                if ($Logger) { $Logger.AddInfoRecord("[i] Preparing template - adding HTML Links for $($links.Key)") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "Links for", " $($links.Key)..." -Color White, Yellow, White, White, Yellow, White }
                $Body = $Body -replace "<<$($links.Key)>>", "<span style=color:$($link.Color)><a href='$($link.Link)'>$($Link.Text)</a></span>"
            }
        }
    }
    if ($AddAfterOpening) { $Body += $AddBeforeClosing }
    $Body += '</body>'
    if ($ConfigurationParameters) { if ($ConfigurationParameters.DisplayTemplateHTML -eq $true) { Get-HTML($Body) } }
    return $Body
}
function Set-EmailHead {
    param([System.Collections.IDictionary] $FormattingOptions)
    $head = @"
    <head>
        <style>
        BODY {
            background-color: white;
            font-family: $($FormattingOptions.FontFamily);
            font-size: $($FormattingOptions.FontSize);
        }
 
        TABLE {
            border-width: 1px;
            border-style: solid;
            border-color: black;
            border-collapse: collapse;
            font-family: $($FormattingOptions.FontTableDataFamily);
            font-size: $($FormattingOptions.FontTableDataSize);
        }
 
        TH {
            border-width: 1px;
            padding: 3px;
            border-style: solid;
            border-color: black;
            background-color: #00297A;
            color: white;
            font-family: $($FormattingOptions.FontTableHeadingFamily);
            font-size: $($FormattingOptions.FontTableHeadingSize);
        }
        TR {
            font-family: $($FormattingOptions.FontTableDataFamily);
            font-size: $($FormattingOptions.FontTableDataSize);
        }
 
        UL {
            font-family: $($FormattingOptions.FontFamily);
            font-size: $($FormattingOptions.FontSize);
        }
 
        LI {
            font-family: $($FormattingOptions.FontFamily);
            font-size: $($FormattingOptions.FontSize);
        }
 
        TD {
            border-width: 1px;
            padding-right: 2px;
            padding-left: 2px;
            padding-top: 0px;
            padding-bottom: 0px;
            border-style: solid;
            border-color: black;
            background-color: white;
            font-family: $($FormattingOptions.FontTableDataFamily);
            font-size: $($FormattingOptions.FontTableDataSize);
        }
 
        H2 {
            font-family: $($FormattingOptions.FontHeadingFamily);
            font-size: $($FormattingOptions.FontHeadingSize);
        }
 
        P {
            font-family: $($FormattingOptions.FontFamily);
            font-size: $($FormattingOptions.FontSize);
        }
    </style>
    </head>
"@

    return $Head
}
function Set-EmailReportBranding {
    param([alias('FormattingOptions')] $FormattingParameters)
    if ($FormattingParameters.CompanyBranding.Link) { $Report = "<a style=`"text-decoration:none`" href=`"$($FormattingParameters.CompanyBranding.Link)`" class=`"clink logo-container`">" } else { $Report = '' }
    if ($FormattingParameters.CompanyBranding.Inline) { $Report += "<img width=<fix> height=<fix> src=`"cid:logo`" border=`"0`" class=`"company-logo`" alt=`"company-logo`"></a>" } else { $Report += "<img width=<fix> height=<fix> src=`"$($FormattingParameters.CompanyBranding.Logo)`" border=`"0`" class=`"company-logo`" alt=`"company-logo`"></a>" }
    if ($FormattingParameters.CompanyBranding.Width -ne "") { $Report = $Report -replace "width=<fix>", "width=$($FormattingParameters.CompanyBranding.Width)" } else { $Report = $Report -replace "width=<fix>", "" }
    if ($FormattingParameters.CompanyBranding.Height -ne "") { $Report = $Report -replace "height=<fix>", "height=$($FormattingParameters.CompanyBranding.Height)" } else { $Report = $Report -replace "height=<fix>", "" }
    return $Report
}
function Set-EmailWordReplacements {
    param($Body, $Replace, $ReplaceWith, [switch] $RegEx)
    if ($RegEx) { $Body = $Body -Replace $Replace, $ReplaceWith } else { $Body = $Body.Replace($Replace, $ReplaceWith) }
    return $Body
}
function Set-XML {
    param ([string] $FilePath,
        [string[]]$Paths,
        [string] $Node,
        [string] $Value)
    [xml]$xmlDocument = Get-Content -Path $FilePath -Encoding UTF8
    $XmlElement = $xmlDocument
    foreach ($Path in $Paths) { $XmlElement = $XmlElement.$Path }
    $XmlElement.$Node = $Value
    $xmlDocument.Save($FilePath)
}
function Split-Array {
    [CmdletBinding()]
    <#
        .SYNOPSIS
        Split an array
        .NOTES
        Version : July 2, 2017 - implemented suggestions from ShadowSHarmon for performance
        .PARAMETER inArray
        A one dimensional array you want to split
        .EXAMPLE
        This splits array into multiple arrays of 3
        Example below wil return 1,2,3 + 4,5,6 + 7,8,9
 
        Split-array -inArray @(1,2,3,4,5,6,7,8,9,10) -parts 3
        .EXAMPLE
        This splits array into 3 parts regardless of amount of elements
 
 
        Split-array -inArray @(1,2,3,4,5,6,7,8,9,10) -size 3
 
        # Link: https://gallery.technet.microsoft.com/scriptcenter/Split-an-array-into-parts-4357dcc1
    #>

    param([Object] $inArray,
        [int]$parts,
        [int]$size)
    if ($inArray.Count -eq 1) { return $inArray }
    if ($parts) { $PartSize = [Math]::Ceiling($inArray.count / $parts) }
    if ($size) {
        $PartSize = $size
        $parts = [Math]::Ceiling($inArray.count / $size)
    }
    $outArray = New-Object 'System.Collections.Generic.List[psobject]'
    for ($i = 1; $i -le $parts; $i++) {
        $start = (($i - 1) * $PartSize)
        $end = (($i) * $PartSize) - 1
        if ($end -ge $inArray.count) { $end = $inArray.count - 1 }
        $outArray.Add(@($inArray[$start..$end]))
    }
    return , $outArray
}
function Start-MyProgram {
    [CmdletBinding()]
    param ([string] $Program,
        [string[]] $CmdArgList,
        [System.Collections.IDictionary] $LoggerParameters)
    $Output = (cmd /c $Program $CmdArgList '2>&1')
    if (-not $LoggerParameters) { if ($Output) { return $Output } } else {
        $Logger = Get-Logger @LoggerParameters
        if ($null -ne $Output) { $Logger.AddInfoRecord("Running program $Program with output: $Output") } else { $Logger.AddInfoRecord("Running program $Program $CmdArgList") }
    }
}
function Start-TimeLog {
    [CmdletBinding()]
    param()
    [System.Diagnostics.Stopwatch]::StartNew()
}
function Stop-TimeLog {
    [CmdletBinding()]
    param ([Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time,
        [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner',
        [switch] $Continue)
    Begin { }
    Process { if ($Option -eq 'Array') { $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds" } else { $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" } }
    End {
        if (-not $Continue) { $Time.Stop() }
        return $TimeToExecute
    }
}
function Write-Color {
    <#
    .SYNOPSIS
        Write-Color is a wrapper around Write-Host.
 
        It provides:
        - Easy manipulation of colors,
        - Logging output to file (log)
        - Nice formatting options out of the box.
 
    .DESCRIPTION
        Author: przemyslaw.klys at evotec.pl
        Project website: https://evotec.xyz/hub/scripts/write-color-ps1/
        Project support: https://github.com/EvotecIT/PSWriteColor
 
        Original idea: Josh (https://stackoverflow.com/users/81769/josh)
 
    .EXAMPLE
    Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                    "followed by red ",
                    "and then we have Magenta... ",
                    "isn't it fun? ",
                    "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                    "followed by red ",
                    "and then we have Magenta... ",
                    "isn't it fun? ",
                    "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1
 
    .EXAMPLE
    Write-Color "1. ", "Option 1" -Color Yellow, Green
    Write-Color "2. ", "Option 2" -Color Yellow, Green
    Write-Color "3. ", "Option 3" -Color Yellow, Green
    Write-Color "4. ", "Option 4" -Color Yellow, Green
    Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1
 
    .EXAMPLE
    Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss"
    Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt"
 
    .EXAMPLE
    # Added in 0.5
    Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow
    wc -t "my text" -c yellow -b green
    wc -text "my text" -c red
 
    .NOTES
        Additional Notes:
        - TimeFormat https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
    #>

    [alias('Write-Colour')]
    [CmdletBinding()]
    param ([alias ('T')] [String[]]$Text,
        [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White,
        [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null,
        [alias ('Indent')][int] $StartTab = 0,
        [int] $LinesBefore = 0,
        [int] $LinesAfter = 0,
        [int] $StartSpaces = 0,
        [alias ('L')] [string] $LogFile = '',
        [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss',
        [alias ('LogTimeStamp')][bool] $LogTime = $true,
        [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode',
        [switch] $ShowTime,
        [switch] $NoNewLine)
    $DefaultColor = $Color[0]
    if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) {
        Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated."
        return
    }
    if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host -Object "`n" -NoNewline } }
    if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewLine } }
    if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewLine } }
    if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))]" -NoNewline }
    if ($Text.Count -ne 0) {
        if ($Color.Count -ge $Text.Count) { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewLine } } else { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewLine } } } else {
            if ($null -eq $BackGroundColor) {
                for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewLine }
                for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewLine }
            } else {
                for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewLine }
                for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewLine }
            }
        }
    }
    if ($NoNewLine -eq $true) { Write-Host -NoNewline } else { Write-Host }
    if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } }
    if ($Text.Count -and $LogFile) {
        $TextToFile = ""
        for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] }
        try { if ($LogTime) { "[$([datetime]::Now.ToString($DateTimeFormat))]$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop } else { "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop } } catch { $PSCmdlet.WriteError($_) }
    }
}
function Add-ToHashTable {
    param($Hashtable, $Key, $Value)
    if ($null -ne $Value -and $Value -ne '') { $Hashtable.Add($Key, $Value) }
}
function Compare-MultipleObjects {
    [CmdLetBinding()]
    param([System.Collections.IList] $Objects,
        [switch] $CompareSorted,
        [switch] $FormatOutput,
        [switch] $FormatDifferences,
        [switch] $Summary,
        [string] $Splitter = ', ',
        [string[]] $Property,
        [string[]] $ExcludeProperty,
        [switch] $AllProperties,
        [switch] $SkipProperties,
        [int] $First,
        [int] $Last,
        [Array] $Replace)
    if ($null -eq $Objects -or $Objects.Count -eq 1) {
        Write-Warning "Compare-MultipleObjects - Unable to compare objects. Not enough objects to compare ($($Objects.Count))."
        return
    }
    function Compare-TwoArrays {
        [CmdLetBinding()]
        param([string] $FieldName,
            [Array] $Object1,
            [Array] $Object2,
            [Array] $Replace)
        $Result = [ordered] @{Status = $false
            Same                     = [System.Collections.Generic.List[string]]::new()
            Add                      = [System.Collections.Generic.List[string]]::new()
            Remove                   = [System.Collections.Generic.List[string]]::new()
        }
        if ($Replace) {
            foreach ($R in $Replace) {
                if (($($R.Keys[0]) -eq '') -or ($($R.Keys[0]) -eq $FieldName)) {
                    if ($null -ne $Object1) { $Object1 = $Object1 -replace $($R.Values)[0], $($R.Values)[1] }
                    if ($null -ne $Object2) { $Object2 = $Object2 -replace $($R.Values)[0], $($R.Values)[1] }
                }
            }
        }
        if ($null -eq $Object1 -and $null -eq $Object2) { $Result['Status'] = $true } elseif (($null -eq $Object1) -or ($null -eq $Object2)) {
            $Result['Status'] = $false
            foreach ($O in $Object1) { $Result['Add'].Add($O) }
            foreach ($O in $Object2) { $Result['Remove'].Add($O) }
        } else {
            $ComparedObject = Compare-Object -ReferenceObject $Object1 -DifferenceObject $Object2 -IncludeEqual
            foreach ($_ in $ComparedObject) { if ($_.SideIndicator -eq '==') { $Result['Same'].Add($_.InputObject) } elseif (($_.SideIndicator -eq '<=')) { $Result['Add'].Add($_.InputObject) } elseif (($_.SideIndicator -eq '=>')) { $Result['Remove'].Add($_.InputObject) } }
            IF ($Result['Add'].Count -eq 0 -and $Result['Remove'].Count -eq 0) { $Result['Status'] = $true } else { $Result['Status'] = $false }
        }
        $Result
    }
    if ($First -or $Last) {
        [int] $TotalCount = $First + $Last
        if ($TotalCount -gt 1) { $Objects = $Objects | Select-Object -First $First -Last $Last } else {
            Write-Warning "Compare-MultipleObjects - Unable to compare objects. Not enough objects to compare ($TotalCount)."
            return
        }
    }
    $ReturnValues = @($FirstElement = [ordered] @{ }
        $FirstElement['Name'] = 'Properties'
        if ($Summary) {
            $FirstElement['Same'] = $null
            $FirstElement['Different'] = $null
        }
        $FirstElement['Status'] = $false
        $FirstObjectProperties = Select-Properties -Objects $Objects -Property $Property -ExcludeProperty $ExcludeProperty -AllProperties:$AllProperties
        if (-not $SkipProperties) {
            if ($FormatOutput) { $FirstElement["Source"] = $FirstObjectProperties -join $Splitter } else { $FirstElement["Source"] = $FirstObjectProperties }
            [Array] $IsSame = for ($i = 1; $i -lt $Objects.Count; $i++) {
                if ($Objects[0] -is [System.Collections.IDictionary]) { [string[]] $CompareObjectProperties = $Objects[$i].Keys } else {
                    [string[]] $CompareObjectProperties = $Objects[$i].PSObject.Properties.Name
                    [string[]] $CompareObjectProperties = Select-Properties -Objects $Objects[$i] -Property $Property -ExcludeProperty $ExcludeProperty -AllProperties:$AllProperties
                }
                if ($FormatOutput) { $FirstElement["$i"] = $CompareObjectProperties -join $Splitter } else { $FirstElement["$i"] = $CompareObjectProperties }
                if ($CompareSorted) {
                    $Value1 = $FirstObjectProperties | Sort-Object
                    $Value2 = $CompareObjectProperties | Sort-Object
                } else {
                    $Value1 = $FirstObjectProperties
                    $Value2 = $CompareObjectProperties
                }
                $Status = Compare-TwoArrays -FieldName 'Properties' -Object1 $Value1 -Object2 $Value2 -Replace $Replace
                if ($FormatDifferences) {
                    $FirstElement["$i-Add"] = $Status['Add'] -join $Splitter
                    $FirstElement["$i-Remove"] = $Status['Remove'] -join $Splitter
                    $FirstElement["$i-Same"] = $Status['Same'] -join $Splitter
                } else {
                    $FirstElement["$i-Add"] = $Status['Add']
                    $FirstElement["$i-Remove"] = $Status['Remove']
                    $FirstElement["$i-Same"] = $Status['Same']
                }
                $Status
            }
            if ($IsSame.Status -notcontains $false) { $FirstElement['Status'] = $true } else { $FirstElement['Status'] = $false }
            if ($Summary) {
                [Array] $Collection = (0..($IsSame.Count - 1)).Where( { $IsSame[$_].Status -eq $true }, 'Split')
                if ($FormatDifferences) {
                    $FirstElement['Same'] = ($Collection[0] | ForEach-Object { $_ + 1 }) -join $Splitter
                    $FirstElement['Different'] = ($Collection[1] | ForEach-Object { $_ + 1 }) -join $Splitter
                } else {
                    $FirstElement['Same'] = $Collection[0] | ForEach-Object { $_ + 1 }
                    $FirstElement['Different'] = $Collection[1] | ForEach-Object { $_ + 1 }
                }
            }
            [PSCustomObject] $FirstElement
        }
        foreach ($_ in $FirstObjectProperties) {
            $EveryOtherElement = [ordered] @{ }
            $EveryOtherElement['Name'] = $_
            if ($Summary) {
                $EveryOtherElement['Same'] = $null
                $EveryOtherElement['Different'] = $null
            }
            $EveryOtherElement.Status = $false
            if ($FormatOutput) { $EveryOtherElement['Source'] = $Objects[0].$_ -join $Splitter } else { $EveryOtherElement['Source'] = $Objects[0].$_ }
            [Array] $IsSame = for ($i = 1; $i -lt $Objects.Count; $i++) {
                if ($FormatOutput) { $EveryOtherElement["$i"] = $Objects[$i].$_ -join $Splitter } else { $EveryOtherElement["$i"] = $Objects[$i].$_ }
                if ($CompareSorted) {
                    $Value1 = $Objects[0].$_ | Sort-Object
                    $Value2 = $Objects[$i].$_ | Sort-Object
                } else {
                    $Value1 = $Objects[0].$_
                    $Value2 = $Objects[$i].$_
                }
                $Status = Compare-TwoArrays -FieldName $_ -Object1 $Value1 -Object2 $Value2 -Replace $Replace
                if ($FormatDifferences) {
                    $EveryOtherElement["$i-Add"] = $Status['Add'] -join $Splitter
                    $EveryOtherElement["$i-Remove"] = $Status['Remove'] -join $Splitter
                    $EveryOtherElement["$i-Same"] = $Status['Same'] -join $Splitter
                } else {
                    $EveryOtherElement["$i-Add"] = $Status['Add']
                    $EveryOtherElement["$i-Remove"] = $Status['Remove']
                    $EveryOtherElement["$i-Same"] = $Status['Same']
                }
                $Status
            }
            if ($IsSame.Status -notcontains $false) { $EveryOtherElement['Status'] = $true } else { $EveryOtherElement['Status'] = $false }
            if ($Summary) {
                [Array] $Collection = (0..($IsSame.Count - 1)).Where( { $IsSame[$_].Status -eq $true }, 'Split')
                if ($FormatDifferences) {
                    $EveryOtherElement['Same'] = ($Collection[0] | ForEach-Object { $_ + 1 }) -join $Splitter
                    $EveryOtherElement['Different'] = ($Collection[1] | ForEach-Object { $_ + 1 }) -join $Splitter
                } else {
                    $EveryOtherElement['Same'] = $Collection[0] | ForEach-Object { $_ + 1 }
                    $EveryOtherElement['Different'] = $Collection[1] | ForEach-Object { $_ + 1 }
                }
            }
            [PSCuStomObject] $EveryOtherElement
        })
    if ($ReturnValues.Count -eq 1) { return , $ReturnValues } else { return $ReturnValues }
}
function Convert-Color {
    <#
    .Synopsis
    This color converter gives you the hexadecimal values of your RGB colors and vice versa (RGB to HEX)
    .Description
    This color converter gives you the hexadecimal values of your RGB colors and vice versa (RGB to HEX). Use it to convert your colors and prepare your graphics and HTML web pages.
    .Parameter RBG
    Enter the Red Green Blue value comma separated. Red: 51 Green: 51 Blue: 204 for example needs to be entered as 51,51,204
    .Parameter HEX
    Enter the Hex value to be converted. Do not use the '#' symbol. (Ex: 3333CC converts to Red: 51 Green: 51 Blue: 204)
    .Example
    .\convert-color -hex FFFFFF
    Converts hex value FFFFFF to RGB
 
    .Example
    .\convert-color -RGB 123,200,255
    Converts Red = 123 Green = 200 Blue = 255 to Hex value
 
    #>

    param([Parameter(ParameterSetName = "RGB", Position = 0)]
        [ValidateScript( { $_ -match '^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$' })]
        $RGB,
        [Parameter(ParameterSetName = "HEX", Position = 0)]
        [ValidateScript( { $_ -match '[A-Fa-f0-9]{6}' })]
        [string]
        $HEX)
    switch ($PsCmdlet.ParameterSetName) {
        "RGB" {
            if ($null -eq $RGB[2]) { Write-Error "Value missing. Please enter all three values seperated by comma." }
            $red = [convert]::Tostring($RGB[0], 16)
            $green = [convert]::Tostring($RGB[1], 16)
            $blue = [convert]::Tostring($RGB[2], 16)
            if ($red.Length -eq 1) { $red = '0' + $red }
            if ($green.Length -eq 1) { $green = '0' + $green }
            if ($blue.Length -eq 1) { $blue = '0' + $blue }
            Write-Output $red$green$blue
        }
        "HEX" {
            $red = $HEX.Remove(2, 4)
            $Green = $HEX.Remove(4, 2)
            $Green = $Green.remove(0, 2)
            $Blue = $hex.Remove(0, 4)
            $Red = [convert]::ToInt32($red, 16)
            $Green = [convert]::ToInt32($green, 16)
            $Blue = [convert]::ToInt32($blue, 16)
            Write-Output $red, $Green, $blue
        }
    }
}
function ConvertTo-OperatingSystem {
    [CmdletBinding()]
    param([string] $OperatingSystem,
        [string] $OperatingSystemVersion)
    if ($OperatingSystem -like '*Windows 10*') {
        $Systems = @{'10.0 (18363)' = "Windows 10 1909"
            '10.0 (18362)'          = "Windows 10 1903"
            '10.0 (17763)'          = "Windows 10 1809"
            '10.0 (17134)'          = "Windows 10 1803"
            '10.0 (16299)'          = "Windows 10 1709"
            '10.0 (15063)'          = "Windows 10 1703"
            '10.0 (14393)'          = "Windows 10 1607"
            '10.0 (10586)'          = "Windows 10 1511"
            '10.0 (10240)'          = "Windows 10 1507"
            '10.0 (18898)'          = 'Windows 10 Insider Preview'
            '10.0.18363'            = "Windows 10 1909"
            '10.0.18362'            = "Windows 10 1903"
            '10.0.17763'            = "Windows 10 1809"
            '10.0.17134'            = "Windows 10 1803"
            '10.0.16299'            = "Windows 10 1709"
            '10.0.15063'            = "Windows 10 1703"
            '10.0.14393'            = "Windows 10 1607"
            '10.0.10586'            = "Windows 10 1511"
            '10.0.10240'            = "Windows 10 1507"
            '10.0.18898'            = 'Windows 10 Insider Preview'
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) { $System = $OperatingSystem }
    } elseif ($OperatingSystem -like '*Windows Server*') {
        $Systems = @{'5.2 (3790)' = 'Windows Server 2003'
            '6.1 (7601)'          = 'Windows Server 2008 R2'
            '10.0 (18362)'        = "Windows Server, version 1903 (Semi-Annual Channel) 1903"
            '10.0 (17763)'        = "Windows Server 2019 (Long-Term Servicing Channel) 1809"
            '10.0 (17134)'        = "Windows Server, version 1803 (Semi-Annual Channel) 1803"
            '10.0 (14393)'        = "Windows Server 2016 (Long-Term Servicing Channel) 1607"
            '10.0.18362'          = "Windows Server, version 1903 (Semi-Annual Channel) 1903"
            '10.0.17763'          = "Windows Server 2019 (Long-Term Servicing Channel) 1809"
            '10.0.17134'          = "Windows Server, version 1803 (Semi-Annual Channel) 1803"
            '10.0.14393'          = "Windows Server 2016 (Long-Term Servicing Channel) 1607"
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) { $System = $OperatingSystem }
    } else { $System = $OperatingSystem }
    if ($System) { $System } else { 'Unknown' }
}
function Format-TransposeTable {
    [CmdletBinding()]
    param ([Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][System.Collections.ICollection] $Object,
        [ValidateSet("ASC", "DESC", "NONE")][String] $Sort = 'NONE')
    begin { $i = 0 }
    process {
        foreach ($myObject in $Object) {
            if ($myObject -is [System.Collections.IDictionary]) {
                $output = New-Object -TypeName PsObject
                Add-Member -InputObject $output -MemberType ScriptMethod -Name AddNote -Value { Add-Member -InputObject $this -MemberType NoteProperty -Name $args[0] -Value $args[1] }
                if ($Sort -eq 'ASC') { $myObject.Keys | Sort-Object -Descending:$false | ForEach-Object { $output.AddNote($_, $myObject.$_) } } elseif ($Sort -eq 'DESC') { $myObject.Keys | Sort-Object -Descending:$true | ForEach-Object { $output.AddNote($_, $myObject.$_) } } else { $myObject.Keys | ForEach-Object { $output.AddNote($_, $myObject.$_) } }
                $output
            } else {
                $output = [ordered] @{ }
                $myObject | Get-Member -MemberType *Property | ForEach-Object { $output.($_.name) = $myObject.($_.name) }
                $output
            }
            $i += 1
        }
    }
}
function Get-HTML {
    [CmdletBinding()]
    param ($text)
    $text = $text.Split("`r")
    foreach ($t in $text) { Write-Host $t }
}
function Get-MimeType {
    [CmdletBinding()]
    param ([Parameter(Mandatory = $true)]
        [string] $FileName)
    $MimeMappings = @{'.jpeg' = 'image/jpeg'
        '.jpg'                = 'image/jpeg'
        '.png'                = 'image/png'
    }
    $Extension = [System.IO.Path]::GetExtension($FileName)
    $ContentType = $MimeMappings[ $Extension ]
    if ([string]::IsNullOrEmpty($ContentType)) { return New-Object System.Net.Mime.ContentType } else { return New-Object System.Net.Mime.ContentType($ContentType) }
}
function Get-ObjectPropertiesAdvanced {
    [CmdletBinding()]
    param ([object] $Object,
        [string[]] $AddProperties,
        [switch] $Sort)
    $Data = @{ }
    $Properties = New-ArrayList
    $HighestCount = 0
    foreach ($O in $Object) {
        $ObjectProperties = $O.PSObject.Properties.Name
        $Count = $ObjectProperties.Count
        if ($Count -gt $HighestCount) {
            $Data.HighestCount = $Count
            $Data.HighestObject = $O
            $HighestCount = $Count
        }
        foreach ($Property in $ObjectProperties) { Add-ToArrayAdvanced -List $Properties -Element $Property -SkipNull -RequireUnique }
    }
    foreach ($Property in $AddProperties) { Add-ToArrayAdvanced -List $Properties -Element $Property -SkipNull -RequireUnique }
    $Data.Properties = if ($Sort) { $Properties | Sort-Object } else { $Properties }
    return $Data
}
function Get-SqlQueryColumnInformation {
    [CmdletBinding()]
    param ([string] $SqlServer,
        [string] $SqlDatabase,
        [string] $Table)
    $Table = $Table.Replace("dbo.", '').Replace('[', '').Replace(']', '')
    $SqlDatabase = $SqlDatabase.Replace('[', '').Replace(']', '')
    $SqlDatabase = "[$SqlDatabase]"
    $Query = "SELECT * FROM $SqlDatabase.INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$Table'"
    $SqlReturn = @(try { Invoke-DbaQuery -ErrorAction Stop -SqlInstance $SqlServer -Query $Query } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            "Error occured (Get-SqlQueryColumnInformation): $ErrorMessage"
        })
    return $SQLReturn
}
function New-Runspace {
    [cmdletbinding()]
    param ([int] $minRunspaces = 1,
        [int] $maxRunspaces = [int]$env:NUMBER_OF_PROCESSORS + 1)
    $RunspacePool = [RunspaceFactory]::CreateRunspacePool($minRunspaces, $maxRunspaces)
    $RunspacePool.Open()
    return $RunspacePool
}
function New-SqlQuery {
    [CmdletBinding()]
    param ([Object] $SqlSettings,
        [Object] $Object,
        [Object] $TableMapping)
    $ArraySQLQueries = New-ArrayList
    if ($null -ne $Object) {
        foreach ($O in $Object) {
            $ArrayMain = New-ArrayList
            $ArrayKeys = New-ArrayList
            $ArrayValues = New-ArrayList
            if (-not $O.AddedWhen) { Add-Member -InputObject $O -MemberType NoteProperty -Name "AddedWhen" -Value (Get-Date) -Force }
            if (-not $O.AddedWho) { Add-Member -InputObject $O -MemberType NoteProperty -Name "AddedWho" -Value ($Env:USERNAME) -Force }
            $DuplicateString = [System.Text.StringBuilder]::new()
            foreach ($E in $O.PSObject.Properties) {
                $FieldName = $E.Name
                $FieldValue = $E.Value
                foreach ($MapKey in $TableMapping.Keys) {
                    if ($FieldName -eq $MapKey) {
                        $MapValue = $TableMapping.$MapKey
                        $MapValueSplit = $MapValue -Split ','
                        if ($FieldValue -is [DateTime]) { $FieldValue = Get-Date $FieldValue -Format "yyyy-MM-dd HH:mm:ss" }
                        if ($FieldValue -like "*'*") { $FieldValue = $FieldValue -Replace "'", "''" }
                        Add-ToArray -List $ArrayKeys -Element "[$($MapValueSplit[0])]"
                        if ([string]::IsNullOrWhiteSpace($FieldValue)) { Add-ToArray -List $ArrayValues -Element "NULL" } else {
                            foreach ($ColumnName in $SqlSettings.SqlCheckBeforeInsert) {
                                $DuplicateColumn = $ColumnName.Replace("[", '').Replace("]", '')
                                if ($MapValueSplit[0] -eq $DuplicateColumn) {
                                    if ($DuplicateString.Length -ne 0) { $null = $DuplicateString.Append(" AND ") }
                                    $null = $DuplicateString.Append("[$DuplicateColumn] = '$FieldValue'")
                                }
                            }
                            Add-ToArray -List $ArrayValues -Element "'$FieldValue'"
                        }
                    }
                }
            }
            if ($ArrayKeys) {
                if ($null -ne $SqlSettings.SqlCheckBeforeInsert -and $DuplicateString.Length -gt 0) {
                    Add-ToArray -List $ArrayMain -Element "IF NOT EXISTS ("
                    Add-ToArray -List $ArrayMain -Element "SELECT 1 FROM "
                    Add-ToArray -List $ArrayMain -Element "$($SqlSettings.SqlTable) "
                    Add-ToArray -List $ArrayMain -Element "WHERE $($DuplicateString.ToString())"
                    Add-ToArray -List $ArrayMain -Element ")"
                }
                Add-ToArray -List $ArrayMain -Element "BEGIN"
                Add-ToArray -List $ArrayMain -Element "INSERT INTO $($SqlSettings.SqlTable) ("
                Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',')
                Add-ToArray -List $ArrayMain -Element ') VALUES ('
                Add-ToArray -List $ArrayMain -Element ($ArrayValues -join ',')
                Add-ToArray -List $ArrayMain -Element ')'
                Add-ToArray -List $ArrayMain -Element "END"
                Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "")
            }
        }
    }
    return $ArraySQLQueries
}
function New-SqlQueryAlterTable {
    [CmdletBinding()]
    param ([Object]$SqlSettings,
        [Object]$TableMapping,
        [string[]] $ExistingColumns)
    $ArraySQLQueries = New-ArrayList
    $ArrayMain = New-ArrayList
    $ArrayKeys = New-ArrayList
    foreach ($MapKey in $TableMapping.Keys) {
        $MapValue = $TableMapping.$MapKey
        $Field = $MapValue -Split ','
        if ($ExistingColumns -notcontains $MapKey -and $ExistingColumns -notcontains $Field[0]) { if ($Field.Count -eq 1) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] [nvarchar](max) NULL" } elseif ($Field.Count -eq 2) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) NULL" } elseif ($Field.Count -eq 3) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) $($Field[2])" } }
    }
    if ($ArrayKeys) {
        Add-ToArray -List $ArrayMain -Element "ALTER TABLE $($SqlSettings.SqlTable) ADD"
        Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',')
        Add-ToArray -List $ArrayMain -Element ';'
        Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "")
    }
    return $ArraySQLQueries
}
function New-SqlQueryCreateTable {
    [CmdletBinding()]
    param ([Object]$SqlSettings,
        [Object]$TableMapping)
    $ArraySQLQueries = New-ArrayList
    $ArrayMain = New-ArrayList
    $ArrayKeys = New-ArrayList
    foreach ($MapKey in $TableMapping.Keys) {
        $MapValue = $TableMapping.$MapKey
        $Field = $MapValue -Split ','
        if ($Field.Count -eq 1) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] [nvarchar](max) NULL" } elseif ($Field.Count -eq 2) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) NULL" } elseif ($Field.Count -eq 3) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) $($Field[2])" }
    }
    if ($ArrayKeys) {
        Add-ToArray -List $ArrayMain -Element "CREATE TABLE $($SqlSettings.SqlTable) ("
        Add-ToArray -List $ArrayMain -Element "ID int IDENTITY(1,1) PRIMARY KEY,"
        Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',')
        Add-ToArray -List $ArrayMain -Element ')'
        Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "")
    }
    return $ArraySQLQueries
}
function New-SqlTableMapping {
    [CmdletBinding()]
    param([Object] $SqlTableMapping,
        [Object] $Object,
        $Properties,
        [switch] $BasedOnSqlTable)
    if ($SqlTableMapping) { $TableMapping = $SqlTableMapping } else {
        $TableMapping = @{ }
        if ($BasedOnSqlTable) {
            foreach ($Property in $Properties) {
                $FieldName = $Property
                $FieldNameSql = $Property
                $TableMapping.$FieldName = $FieldNameSQL
            }
        } else {
            foreach ($O in $Properties.HighestObject) {
                foreach ($Property in $Properties.Properties) {
                    $FieldName = $Property
                    $FieldValue = $O.$Property
                    $FieldNameSQL = $FieldName.Replace(' ', '')
                    if ($FieldValue -is [DateTime]) { $TableMapping.$FieldName = "$FieldNameSQL,[datetime],null" } elseif ($FieldValue -is [int] -or $FieldValue -is [Int64]) { $TableMapping.$FieldName = "$FieldNameSQL,[bigint]" } elseif ($FieldValue -is [bool]) { $TableMapping.$FieldName = "$FieldNameSQL,[bit]" } else { $TableMapping.$FieldName = "$FieldNameSQL" }
                }
            }
        }
    }
    return $TableMapping
}
function Request-Credentials {
    [CmdletBinding()]
    param([string] $UserName,
        [string] $Password,
        [switch] $AsSecure,
        [switch] $FromFile,
        [switch] $Output,
        [switch] $NetworkCredentials,
        [string] $Service)
    if ($FromFile) {
        if (($Password -ne '') -and (Test-Path $Password)) {
            Write-Verbose "Request-Credentials - Reading password from file $Password"
            $Password = Get-Content -Path $Password
        } else {
            if ($Output) { return @{Status = $false; Output = $Service; Extended = 'File with password unreadable.' } } else {
                Write-Warning "Request-Credentials - Secure password from file couldn't be read. File not readable. Terminating."
                return
            }
        }
    }
    if ($AsSecure) {
        try { $NewPassword = $Password | ConvertTo-SecureString -ErrorAction Stop } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            if ($ErrorMessage -like '*Key not valid for use in specified state*') {
                if ($Output) { return @{Status = $false; Output = $Service; Extended = "Couldn't use credentials provided. Most likely using credentials from other user/session/computer." } } else {
                    Write-Warning -Message "Request-Credentials - Couldn't use credentials provided. Most likely using credentials from other user/session/computer."
                    return
                }
            } else {
                if ($Output) { return @{Status = $false; Output = $Service; Extended = $ErrorMessage } } else {
                    Write-Warning -Message "Request-Credentials - $ErrorMessage"
                    return
                }
            }
        }
    } else { $NewPassword = $Password }
    if ($UserName -and $NewPassword) {
        if ($AsSecure) { $Credentials = New-Object System.Management.Automation.PSCredential($Username, $NewPassword) } else {
            Try { $SecurePassword = $Password | ConvertTo-SecureString -asPlainText -Force -ErrorAction Stop } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                if ($ErrorMessage -like '*Key not valid for use in specified state*') {
                    if ($Output) { return @{Status = $false; Output = $Service; Extended = "Couldn't use credentials provided. Most likely using credentials from other user/session/computer." } } else {
                        Write-Warning -Message "Request-Credentials - Couldn't use credentials provided. Most likely using credentials from other user/session/computer."
                        return
                    }
                } else {
                    if ($Output) { return @{Status = $false; Output = $Service; Extended = $ErrorMessage } } else {
                        Write-Warning -Message "Request-Credentials - $ErrorMessage"
                        return
                    }
                }
            }
            $Credentials = New-Object System.Management.Automation.PSCredential($Username, $SecurePassword)
        }
    } else {
        if ($Output) { return @{Status = $false; Output = $Service; Extended = 'Username or/and Password is empty' } } else {
            Write-Warning -Message 'Request-Credentials - UserName or Password are empty.'
            return
        }
    }
    if ($NetworkCredentials) { return $Credentials.GetNetworkCredential() } else { return $Credentials }
}
function Select-Properties {
    [CmdLetBinding()]
    param([Array] $Objects,
        [string[]] $Property,
        [string[]] $ExcludeProperty,
        [switch] $AllProperties)
    function Select-Unique {
        [CmdLetBinding()]
        param([System.Collections.IList] $Object)
        $New = $Object.ToLower() | Select-Object -Unique
        $Selected = foreach ($_ in $New) {
            $Index = $Object.ToLower().IndexOf($_)
            if ($Index -ne -1) { $Object[$Index] }
        }
        $Selected
    }
    if ($Objects.Count -eq 0) {
        Write-Warning 'Select-Properties - Unable to process. Objects count equals 0.'
        return
    }
    if ($Objects[0] -is [System.Collections.IDictionary]) {
        if ($AllProperties) {
            [Array] $All = foreach ($_ in $Objects) { $_.Keys }
            $FirstObjectProperties = Select-Unique -Object $All
        } else { $FirstObjectProperties = $Objects[0].Keys }
        if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) {
            $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                if ($Property -contains $_ -and $ExcludeProperty -notcontains $_) {
                    $_
                    continue
                }
            }
        } elseif ($Property.Count -gt 0) {
            $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                if ($Property -contains $_) {
                    $_
                    continue
                }
            }
        } elseif ($ExcludeProperty.Count -gt 0) {
            $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                if ($ExcludeProperty -notcontains $_) {
                    $_
                    continue
                }
            }
        }
    } else {
        if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) { $Objects = $Objects | Select-Object -Property $Property -ExcludeProperty $ExcludeProperty } elseif ($Property.Count -gt 0) { $Objects = $Objects | Select-Object -Property $Property } elseif ($ExcludeProperty.Count -gt 0) { $Objects = $Objects | Select-Object -Property '*' -ExcludeProperty $ExcludeProperty }
        if ($AllProperties) {
            [Array] $All = foreach ($_ in $Objects) { $_.PSObject.Properties.Name }
            $FirstObjectProperties = Select-Unique -Object $All
        } else { $FirstObjectProperties = $Objects[0].PSObject.Properties.Name }
    }
    return $FirstObjectProperties
}
function Start-Runspace {
    [cmdletbinding()]
    param ([ScriptBlock] $ScriptBlock,
        [System.Collections.IDictionary] $Parameters,
        [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool)
    if ($ScriptBlock -ne '') {
        $runspace = [PowerShell]::Create()
        $null = $runspace.AddScript($ScriptBlock)
        if ($null -ne $Parameters) { $null = $runspace.AddParameters($Parameters) }
        $runspace.RunspacePool = $RunspacePool
        [PSCustomObject]@{Pipe = $runspace
            Status             = $runspace.BeginInvoke()
        }
    }
}
function Stop-Runspace {
    [cmdletbinding()]
    param([Array] $Runspaces,
        [string] $FunctionName,
        [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool,
        [switch] $ExtendedOutput)
    [Array] $List = While (@($Runspaces | Where-Object -FilterScript { $null -ne $_.Status }).count -gt 0) {
        foreach ($Runspace in $Runspaces | Where-Object { $_.Status.IsCompleted -eq $true }) {
            $Errors = foreach ($e in $($Runspace.Pipe.Streams.Error)) {
                Write-Error -ErrorRecord $e
                $e
            }
            foreach ($w in $($Runspace.Pipe.Streams.Warning)) { Write-Warning -Message $w }
            foreach ($v in $($Runspace.Pipe.Streams.Verbose)) { Write-Verbose -Message $v }
            if ($ExtendedOutput) {
                @{Output   = $Runspace.Pipe.EndInvoke($Runspace.Status)
                    Errors = $Errors
                }
            } else { $Runspace.Pipe.EndInvoke($Runspace.Status) }
            $Runspace.Status = $null
        }
    }
    $RunspacePool.Close()
    $RunspacePool.Dispose()
    if ($List.Count -eq 1) { return , $List } else { return $List }
}
function Test-ComputerPort {
    [CmdletBinding()]
    param ([alias('Server')][string[]] $ComputerName,
        [int[]] $PortTCP,
        [int[]] $PortUDP,
        [int]$Timeout = 5000)
    begin {
        if ($Global:ProgressPreference -ne 'SilentlyContinue') {
            $TemporaryProgress = $Global:ProgressPreference
            $Global:ProgressPreference = 'SilentlyContinue'
        }
    }
    process {
        foreach ($Computer in $ComputerName) {
            foreach ($P in $PortTCP) {
                $Output = [ordered] @{'ComputerName' = $Computer
                    'Port'                           = $P
                    'Protocol'                       = 'TCP'
                    'Status'                         = $null
                    'Summary'                        = $null
                    'Response'                       = $null
                }
                $TcpClient = Test-NetConnection -ComputerName $Computer -Port $P -InformationLevel Detailed -WarningAction SilentlyContinue
                if ($TcpClient.TcpTestSucceeded) {
                    $Output['Status'] = $TcpClient.TcpTestSucceeded
                    $Output['Summary'] = "TCP $P Successful"
                } else {
                    $Output['Status'] = $false
                    $Output['Summary'] = "TCP $P Failed"
                    $Output['Response'] = $Warnings
                }
                [PSCustomObject]$Output
            }
            foreach ($P in $PortUDP) {
                $Output = [ordered] @{'ComputerName' = $Computer
                    'Port'                           = $P
                    'Protocol'                       = 'UDP'
                    'Status'                         = $null
                    'Summary'                        = $null
                }
                $UdpClient = [System.Net.Sockets.UdpClient]::new($Computer, $P)
                $UdpClient.Client.ReceiveTimeout = $Timeout
                $Encoding = [System.Text.ASCIIEncoding]::new()
                $byte = $Encoding.GetBytes("Evotec")
                [void]$UdpClient.Send($byte, $byte.length)
                $RemoteEndpoint = [System.Net.IPEndPoint]::new([System.Net.IPAddress]::Any, 0)
                try {
                    $Bytes = $UdpClient.Receive([ref]$RemoteEndpoint)
                    [string]$Data = $Encoding.GetString($Bytes)
                    If ($Data) {
                        $Output['Status'] = $true
                        $Output['Summary'] = "UDP $P Successful"
                        $Output['Response'] = $Data
                    }
                } catch {
                    $Output['Status'] = $false
                    $Output['Summary'] = "UDP $P Failed"
                    $Output['Response'] = $_.Exception.Message
                }
                $UdpClient.Close()
                $UdpClient.Dispose()
                [PSCustomObject]$Output
            }
        }
    }
    end { if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } }
}
function Test-WinRM {
    [CmdletBinding()]
    param ([alias('Server')][string[]] $ComputerName)
    $Output = foreach ($Computer in $ComputerName) {
        $Test = [PSCustomObject] @{Output = $null
            Status                        = $null
            ComputerName                  = $Computer
        }
        try {
            $Test.Output = Test-WSMan -ComputerName $Computer -ErrorAction Stop
            $Test.Status = $true
        } catch { $Test.Status = $false }
        $Test
    }
    $Output
}
function Add-ToArray {
    [CmdletBinding()]
    param([System.Collections.ArrayList] $List,
        [Object] $Element)
    [void] $List.Add($Element)
}
function Add-ToArrayAdvanced {
    [CmdletBinding()]
    param([System.Collections.ArrayList] $List,
        [Object] $Element,
        [switch] $SkipNull,
        [switch] $RequireUnique,
        [switch] $FullComparison,
        [switch] $Merge)
    if ($SkipNull -and $null -eq $Element) { return }
    if ($RequireUnique) {
        if ($FullComparison) {
            foreach ($ListElement in $List) {
                if ($ListElement -eq $Element) {
                    $TypeLeft = Get-ObjectType -Object $ListElement
                    $TypeRight = Get-ObjectType -Object $Element
                    if ($TypeLeft.ObjectTypeName -eq $TypeRight.ObjectTypeName) { return }
                }
            }
        } else { if ($List -contains $Element) { return } }
    }
    if ($Merge) { [void] $List.AddRange($Element) } else { [void] $List.Add($Element) }
}
function Format-PSTable {
    [CmdletBinding()]
    param ([parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)][System.Collections.ICollection] $Object,
        [switch] $SkipTitle,
        [string[]] $Property,
        [string[]] $ExcludeProperty,
        [Object] $OverwriteHeaders,
        [switch] $PreScanHeaders,
        [string] $Splitter = ';')
    if ($Object[0] -is [System.Collections.IDictionary]) {
        $Array = @(if (-not $SkipTitle) { , @('Name', 'Value') }
            foreach ($O in $Object) {
                foreach ($Name in $O.Keys) {
                    $Value = $O[$Name]
                    if ($O[$Name].Count -gt 1) { $Value = $O[$Name] -join $Splitter } else { $Value = $O[$Name] }
                    , @($Name, $Value)
                }
            })
        if ($Array.Count -eq 1) { , $Array } else { $Array }
    } elseif ($Object[0].GetType().Name -match 'bool|byte|char|datetime|decimal|double|ExcelHyperLink|float|int|long|sbyte|short|string|timespan|uint|ulong|URI|ushort') { return $Object } else {
        if ($Property) { $Object = $Object | Select-Object -Property $Property }
        $Array = @(if ($PreScanHeaders) { $Titles = Get-ObjectProperties -Object $Object } elseif ($OverwriteHeaders) { $Titles = $OverwriteHeaders } else { $Titles = $Object[0].PSObject.Properties.Name }
            if (-not $SkipTitle) { , $Titles }
            foreach ($O in $Object) {
                $ArrayValues = foreach ($Name in $Titles) {
                    $Value = $O."$Name"
                    if ($Value.Count -gt 1) { $Value -join $Splitter } elseif ($Value.Count -eq 1) { if ($Value.Value) { $Value.Value } else { $Value } } else { '' }
                }
                , $ArrayValues
            })
        if ($Array.Count -eq 1) { , $Array } else { $Array }
    }
}
function Get-FileName {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Extension
    Parameter description
 
    .PARAMETER Temporary
    Parameter description
 
    .PARAMETER TemporaryFileOnly
    Parameter description
 
    .EXAMPLE
    Get-FileName -Temporary
    Output: 3ymsxvav.tmp
 
    .EXAMPLE
 
    Get-FileName -Temporary
    Output: C:\Users\pklys\AppData\Local\Temp\tmpD74C.tmp
 
    .EXAMPLE
 
    Get-FileName -Temporary -Extension 'xlsx'
    Output: C:\Users\pklys\AppData\Local\Temp\tmp45B6.xlsx
 
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string] $Extension = 'tmp',
        [switch] $Temporary,
        [switch] $TemporaryFileOnly)
    if ($Temporary) { return "$($([System.IO.Path]::GetTempFileName()).Replace('.tmp','')).$Extension" }
    if ($TemporaryFileOnly) { return "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension" }
}
function New-ArrayList {
    [CmdletBinding()]
    param()
    $List = [System.Collections.ArrayList]::new()
    return , $List
}
function Remove-EmptyValues {
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Hashtable,
        [switch] $Recursive,
        [int] $Rerun)
    foreach ($_ in [string[]] $Hashtable.Keys) { if ($Recursive) { if ($Hashtable[$_] -is [System.Collections.IDictionary]) { if ($Hashtable[$_].Count -eq 0) { $Hashtable.Remove($_) } else { Remove-EmptyValues -Hashtable $Hashtable[$_] -Recursive:$Recursive } } else { if ($null -eq $Hashtable[$_]) { $Hashtable.Remove($_) } elseif ($Hashtable[$_] -is [string] -and $Hashtable[$_] -eq '') { $Hashtable.Remove($_) } } } else { if ($null -eq $Hashtable[$_]) { $Hashtable.Remove($_) } elseif ($Hashtable[$_] -is [string] -and $Hashtable[$_] -eq '') { $Hashtable.Remove($_) } } }
    if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValues -Hashtable $Hashtable -Recursive:$Recursive } }
}
function Get-ObjectProperties {
    [CmdletBinding()]
    param ([System.Collections.ICollection] $Object,
        [string[]] $AddProperties,
        [switch] $Sort,
        [bool] $RequireUnique = $true)
    $Properties = @(foreach ($O in $Object) {
            $ObjectProperties = $O.PSObject.Properties.Name
            $ObjectProperties
        }
        foreach ($Property in $AddProperties) { $Property })
    if ($Sort) { return $Properties | Sort-Object -Unique:$RequireUnique } else { return $Properties | Select-Object -Unique:$RequireUnique }
}
function Get-ObjectType {
    [CmdletBinding()]
    param([Object] $Object,
        [string] $ObjectName = 'Random Object Name',
        [switch] $VerboseOnly)
    $ReturnData = [ordered] @{ }
    $ReturnData.ObjectName = $ObjectName
    if ($Object -ne $null) {
        try {
            $TypeInformation = $Object.GetType()
            $ReturnData.ObjectTypeName = $TypeInformation.Name
            $ReturnData.ObjectTypeBaseName = $TypeInformation.BaseType
            $ReturnData.SystemType = $TypeInformation.UnderlyingSystemType
        } catch {
            $ReturnData.ObjectTypeName = ''
            $ReturnData.ObjectTypeBaseName = ''
            $ReturnData.SystemType = ''
        }
        try {
            $TypeInformationInsider = $Object[0].GetType()
            $ReturnData.ObjectTypeInsiderName = $TypeInformationInsider.Name
            $ReturnData.ObjectTypeInsiderBaseName = $TypeInformationInsider.BaseType
            $ReturnData.SystemTypeInsider = $TypeInformationInsider.UnderlyingSystemType
        } catch {
            $ReturnData.ObjectTypeInsiderName = ''
            $ReturnData.ObjectTypeInsiderBaseName = ''
            $ReturnData.SystemTypeInsider = ''
        }
    } else {
        $ReturnData.ObjectTypeName = ''
        $ReturnData.ObjectTypeBaseName = ''
        $ReturnData.SystemType = ''
        $ReturnData.ObjectTypeInsiderName = ''
        $ReturnData.ObjectTypeInsiderBaseName = ''
        $ReturnData.SystemTypeInsider = ''
    }
    Write-Verbose "Get-ObjectType - ObjectTypeName: $($ReturnData.ObjectTypeName)"
    Write-Verbose "Get-ObjectType - ObjectTypeBaseName: $($ReturnData.ObjectTypeBaseName)"
    Write-Verbose "Get-ObjectType - SystemType: $($ReturnData.SystemType)"
    Write-Verbose "Get-ObjectType - ObjectTypeInsiderName: $($ReturnData.ObjectTypeInsiderName)"
    Write-Verbose "Get-ObjectType - ObjectTypeInsiderBaseName: $($ReturnData.ObjectTypeInsiderBaseName)"
    Write-Verbose "Get-ObjectType - SystemTypeInsider: $($ReturnData.SystemTypeInsider)"
    if ($VerboseOnly) { return } else { return Format-TransposeTable -Object $ReturnData }
}
function Get-RandomStringName {
    [cmdletbinding()]
    param([int] $Size = 31,
        [switch] $ToLower,
        [switch] $ToUpper,
        [switch] $LettersOnly)
    [string] $MyValue = @(if ($LettersOnly) { ( -join ((1..$Size) | ForEach-Object { (65..90) + (97..122) | Get-Random } | ForEach-Object { [char]$_ })) } else { ( -join ((48..57) + (97..122) | Get-Random -Count $Size | ForEach-Object { [char]$_ })) })
    if ($ToLower) { return $MyValue.ToLower() }
    if ($ToUpper) { return $MyValue.ToUpper() }
    return $MyValue
}
$LdapBindingsDetails = @{Enabled = $false
    Events                       = @{Enabled = $true
        Events                               = 2889
        LogName                              = 'Directory Service'
        IgnoreWords                          = @{ }
        Fields                               = [ordered] @{'Computer' = 'Domain Controller'
            'Action'                            = 'Action'
            'Date'                              = 'When'
            'NoNameA0'                          = 'Ip/Port'
            'NoNameA1'                          = 'Account Name'
            'NoNameA2'                          = 'Bind Type'
            'LevelDisplayName'                  = 'Level'
            'TaskDisplayName'                   = 'Task'
            'ID'                                = 'Event ID'
            'RecordID'                          = 'Record ID'
            'GatheredFrom'                      = 'Gathered From'
            'GatheredLogName'                   = 'Gathered LogName'
        }
        Overwrite                            = [ordered] @{"Bind Type#1" = "Bind Type", 0, "Unsigned"
            "Bind Type#2"                          = "Bind Type", 1, "Simple"
        }
        SortBy                               = 'When'
    }
}
$LdapBindingsSummary = @{Enabled = $false
    Events                       = @{Enabled = $true
        Events                               = 2887
        LogName                              = 'Directory Service'
        IgnoreWords                          = @{ }
        Fields                               = [ordered] @{'Computer' = 'Domain Controller'
            'NoNameA0'                          = 'Number of simple binds performed without SSL/TLS'
            'NoNameA1'                          = 'Number of Negotiate/Kerberos/NTLM/Digest binds performed without signing'
            'Date'                              = 'When'
            'LevelDisplayName'                  = 'Level'
            'TaskDisplayName'                   = 'Task'
            'ID'                                = 'Event ID'
            'RecordID'                          = 'Record ID'
            'GatheredFrom'                      = 'Gathered From'
            'GatheredLogName'                   = 'Gathered LogName'
        }
        SortBy                               = 'When'
    }
}
function Find-EventsTo {
    [CmdletBinding()]
    param ([Array] $Events,
        [alias('IgnoreWords', 'PriorityWords')][System.Collections.IDictionary] $DataSet,
        [switch] $Ignore,
        [switch] $Prioritize)
    if ($DataSet.Count -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 Get-EventsOutput {
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Definitions,
        [Array] $AllEvents,
        [switch] $Quiet)
    $Results = @{ }
    foreach ($Report in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled' }) {
        if ($Definitions.$Report.Enabled) {
            if (-not $Quiet) { $Logger.AddInfoRecord("Running $Report") }
            $TimeExecution = Start-TimeLog
            $Results.$Report = foreach ($SubReport in $Definitions.$Report.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) {
                if ($Definitions.$Report.$SubReport.Enabled) {
                    if (-not $Quiet) { $Logger.AddInfoRecord("Running $Report with subsection $SubReport") }
                    [string] $EventsType = $Definitions.$Report.$SubReport.LogName
                    [Array] $EventsNeeded = $Definitions.$Report.$SubReport.Events
                    [Array] $EventsFound = Get-EventsTranslation -Events $AllEvents -EventsDefinition $Definitions.$Report.$SubReport -EventIDs $EventsNeeded -EventsType $EventsType
                    if (-not $Quiet) { $Logger.AddInfoRecord("Ending $Report with subsection $SubReport events found $($EventsFound.Count)") }
                    $EventsFound
                }
            }
            $ElapsedTimeReport = Stop-TimeLog -Time $TimeExecution -Option OneLiner
            if (-not $Quiet) { $Logger.AddInfoRecord("Ending $Report - Time to run $ElapsedTimeReport") }
        }
    }
    return $Results
}
function Get-EventsTranslation {
    [CmdletBinding()]
    param([Array] $Events,
        [System.Collections.IDictionary] $EventsDefinition,
        [Array] $EventIDs,
        [string] $EventsType)
    if ($EventsDefinition.Filter.Count -gt 0) {
        foreach ($Entry in $EventsDefinition.Filter.Keys) {
            $EveryFilter = $EventsDefinition.Filter[$Entry]
            $StrippedFilter = $Entry -replace '#[0-9]{1,2}', ''
            [Array] $Splitter = $StrippedFilter.Split('#')
            if ($Splitter.Count -gt 1) {
                $PropertyName = $Splitter[0]
                $Operator = $Splitter[1]
            } else {
                $PropertyName = $StrippedFilter
                $Operator = 'eq'
            }
            $Events = foreach ($V in $EveryFilter) { foreach ($_ in $Events) { if ($Operator -eq 'eq') { if ($_.$PropertyName -eq $V) { $_ } } elseif ($Operator -eq 'like') { if ($_.$PropertyName -like $V) { $_ } } elseif ($Operator -eq 'ne') { if ($_.$PropertyName -ne $V) { $_ } } elseif ($Operator -eq 'gt') { if ($_.$PropertyName -gt $V) { $_ } } elseif ($Operator -eq 'lt') { if ($_.$PropertyName -lt $V) { $_ } } } }
        }
    }
    if ($EventsDefinition.FilterOr.Count -gt 0) {
        $Events = foreach ($_ in $Events) {
            foreach ($Entry in $EventsDefinition.FilterOr.Keys) {
                $StrippedFilter = $Entry -replace '#[0-9]{1,2}', ''
                [Array] $Splitter = $StrippedFilter.Split('#')
                if ($Splitter.Count -gt 1) {
                    $PropertyName = $Splitter[0]
                    $Operator = $Splitter[1]
                } else {
                    $PropertyName = $StrippedFilter
                    $Operator = 'eq'
                }
                $Value = $EventsDefinition.FilterOr[$Entry]
                foreach ($V in $Value) { if ($Operator -eq 'eq') { if ($_.$PropertyName -eq $V) { $_ } } elseif ($Operator -eq 'like') { if ($_.$PropertyName -like $V) { $_ } } elseif ($Operator -eq 'ne') { if ($_.$PropertyName -ne $V) { $_ } } elseif ($Operator -eq 'gt') { if ($_.$PropertyName -gt $V) { $_ } } elseif ($Operator -eq 'lt') { if ($_.$PropertyName -lt $V) { $_ } } }
            }
        }
    }
    $MyValue = foreach ($Event in $Events) {
        if (($Event.LogName -eq $EventsType) -and ($Event.ID -in $EventIDs)) { } else { continue }
        $HashTable = [ordered] @{ }
        foreach ($EventProperty in $Event.PSObject.Properties) {
            if ($null -ne $EventsDefinition.Ignore) { if ($EventsDefinition.Ignore.Contains($EventProperty.Name)) { if ($EventProperty.Value -like $EventsDefinition.Ignore[$EventProperty.Name]) { continue } } }
            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)) { $HashTable[$EventsDefinition.Fields[$EventProperty.Name]] = $EventProperty.Value } else { $HashTable[$EventProperty.Name] = $EventProperty.Value }
        }
        if ($null -ne $EventsDefinition.Overwrite) {
            foreach ($Entry in $EventsDefinition.Overwrite.Keys) {
                [Array] $OverwriteObject = $EventsDefinition.Overwrite.$Entry
                $StrippedFilter = $Entry -replace '#[0-9]{1,2}', ''
                [Array] $Splitter = $StrippedFilter.Split('#')
                if ($Splitter.Count -gt 1) {
                    $PropertyName = $Splitter[0]
                    $Operator = $Splitter[1]
                } else {
                    $PropertyName = $StrippedFilter
                    $Operator = 'eq'
                }
                if ($OverwriteObject.Count -eq 3) { if ($Operator -eq 'eq') { if ($HashTable[($OverwriteObject[0])] -eq $OverwriteObject[1]) { $HashTable[$PropertyName] = $OverwriteObject[2] } } elseif ($Operator -eq 'ne') { } elseif ($Operator -eq 'like') { } elseif ($Operator -eq 'gt') { } elseif ($Operator -eq 'lt') { } } elseif ($OverwriteObject.Count -eq 4) { if ($Operator -eq 'eq') { if ($HashTable[($OverwriteObject[0])] -eq $OverwriteObject[1]) { $HashTable[$PropertyName] = $OverwriteObject[2] } else { $HashTable[$PropertyName] = $OverwriteObject[3] } } elseif ($Operator -eq 'ne') { } elseif ($Operator -eq 'like') { } elseif ($Operator -eq 'gt') { } elseif ($Operator -eq 'lt') { } } elseif ($OverwriteObject.Couint -eq 1) { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[0])] }
            }
        }
        if ($null -ne $EventsDefinition.OverwriteByField) {
            foreach ($Entry in $EventsDefinition.OverwriteByField.Keys) {
                [Array] $OverwriteObject = $EventsDefinition.OverwriteByField.$Entry
                $StrippedFilter = $Entry -replace '#[0-9]{1,2}', ''
                [Array] $Splitter = $StrippedFilter.Split('#')
                if ($Splitter.Count -gt 1) {
                    $PropertyName = $Splitter[0]
                    $Operator = $Splitter[1]
                } else {
                    $PropertyName = $StrippedFilter
                    $Operator = 'eq'
                }
                if ($OverwriteObject.Count -eq 3) { if ($Operator -eq 'eq') { if ($HashTable[($OverwriteObject[0])] -eq $OverwriteObject[1]) { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[2])] } } elseif ($Operator -eq 'ne') { if ($HashTable[($OverwriteObject[0])] -ne $OverwriteObject[1]) { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[2])] } } elseif ($Operator -eq 'like') { } elseif ($Operator -eq 'gt') { } elseif ($Operator -eq 'lt') { } } elseif ($OverwriteObject.Count -eq 4) { if ($Operator -eq 'eq') { if ($HashTable[($OverwriteObject[0])] -eq $OverwriteObject[1]) { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[2])] } else { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[3])] } } elseif ($Operator -eq 'ne') { } elseif ($Operator -eq 'like') { } elseif ($Operator -eq 'gt') { } elseif ($Operator -eq 'lt') { } } elseif ($OverwriteObject.Count -eq 1) { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[0])] }
            }
        }
        [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 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) {
        if ($ReportOptions.Contains('AsSql') -and $ReportOptions.AsSql.Use) {
            if ($Report.Contains('EnabledSqlGlobal') -and $Report.EnabledSqlGlobal) {
                $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) {
            $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 {
    [CmdletBinding()]
    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) {
        if ($ReportOptions.Contains('AsSql') -and $ReportOptions.AsSql.Enabled -and $Report.Contains('SqlExport') -and $Report.SqlExport.EnabledGlobal) {
            $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) {
            $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 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
    $Object.AvatarName = $Notifications[$Option].AvatarName
    $Object.AvatarImage = $Notifications[$Option].AvatarImage
    $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 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()
                $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
                if ($Options.Notifications.Slack.Enabled) {
                    $SlackChannel = $Options.Notifications.Slack.$Priority.Channel
                    $SlackColor = ConvertFrom-Color -Color $Slack.Color
                    $FactsSlack = foreach ($Property in $event.PSObject.Properties) { if ($null -ne $Property.Value -and $Property.Value -ne '') { if ($Property.Name -eq 'When') { @{title = $Property.Name; value = $Property.Value.DateTime; short = $true } } else { @{title = $Property.Name; value = $Property.Value; short = $true } } } }
                    $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
                }
                if ($Options.Notifications.MicrosoftTeams.Enabled) {
                    $FactsTeams = foreach ($Property in $event.PSObject.Properties) { if ($null -ne $Property.Value -and $Property.Value -ne '') { if ($Property.Name -eq 'When') { New-TeamsFact -Name $Property.Name -Value $Property.Value.DateTime } else { New-TeamsFact -Name $Property.Name -Value $Property.Value } } }
                    $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
                    Write-Color @script:WriteParameters -Text "[i] Teams output: ", $Data -Color White, Yellow
                }
                if ($Options.Notifications.Discord.Enabled) {
                    $Thumbnail = New-DiscordImage -Url $Discord.ActivityImageLink
                    $FactsDiscord = foreach ($Property in $event.PSObject.Properties) { if ($null -ne $Property.Value -and $Property.Value -ne '') { if ($Property.Name -eq 'When') { New-DiscordFact -Name $Property.Name -Value $Property.Value.DateTime -Inline $true } else { New-DiscordFact -Name $Property.Name -Value $Property.Value -Inline $true } } }
                    $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 $Event -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) {
                    if ($Options.Notifications.Email.AsHTML.Enabled) {
                        $Logger.AddInfoRecord('Prepare email head and body')
                        $HtmlHead = Set-EmailHead -FormattingOptions $Options.Notifications.Email.AsHTML.Formatting
                        $HtmlBody = Set-EmailReportBranding -FormattingParameters $Options.Notifications.Email.AsHTML.Formatting
                        $HtmlBody += Export-ReportToHTML -Report $true -ReportTable $Event -ReportTableText "Quick notification event"
                        $HtmlBody = Set-EmailFormatting -Template $HtmlBody -FormattingParameters $Options.Notifications.Email.AsHTML.Formatting -ConfigurationParameters $Options -Logger $Logger -SkipNewLines
                        $HTML = $HtmlHead + $HtmlBody
                        $EmailBody = $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.Notifications.Email.$Priority.Parameters.Subject
                        $Logger.AddInfoRecord('Sending email with reports')
                        if ($Options.Notifications.Email.AsHTML.Formatting.CompanyBranding.Inline) { $SendMail = Send-Email -EmailParameters $Options.Notifications.Email.$Priority.Parameters -Body $EmailBody -Attachment $AttachedReports -Subject $TemporarySubject -InlineAttachments @{logo = $Options.Notifications.Email.AsHTML.Formatting.CompanyBranding.Logo } -Logger $Logger } else { $SendMail = Send-Email -EmailParameters $Options.Notifications.Email.$Priority.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 $false -ReportFiles $ReportHTMLPath
                    }
                }
            }
        }
    }
    End { }
}
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
    $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 ', ')") } }
    [Array] $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 $($AllEvents.Count) events")
        $AllEvents = Remove-DuplicateObjects -Object $AllEvents -Property $Options.RemoveDuplicates.Properties
        $Logger.AddInfoRecord("Removed Duplicates - following $($AllEvents.Count) events will be analyzed further")
    }
    $Results = Get-EventsOutput -Definitions $Definitions -AllEvents $AllEvents
    if ($Options.AsHTML.Enabled) {
        $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
        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"
        }
        $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-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) {
        $DynamicHTMLPath = Set-ReportFile -Path $Options.AsDynamicHTML.Path -FileNamePattern $Options.AsDynamicHTML.FilePattern -DateFormat $Options.AsDynamicHTML.DateFormat
        $null = New-HTML -TitleText $Options.AsDynamicHTML.Title -UseCssLinks:$Options.AsDynamicHTML.EmbedCSS -UseJavaScriptLinks:$Options.AsDynamicHTML.EmbedJS { foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) {
                $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName
                if ($Definitions.$ReportName.Enabled) { New-HTMLSection -HeaderText $ReportNameTitle -CanCollapse { New-HTMLPanel { if ($null -ne $Results.$ReportName) { New-HTMLTable -DataTable $Results.$ReportName -HideFooter } } } }
            } } -FilePath $DynamicHTMLPath
        try {
            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
        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')
        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) {
        try { if ($ReportHTMLPath -ne '' -and (Test-Path -LiteralPath $ReportHTMLPath)) { Invoke-Item -LiteralPath $ReportHTMLPath } } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            $Logger.AddErrorRecord("Error opening file $ReportHTMLPath.")
        }
    }
    if ($Options.AsDynamicHTML.Enabled -and $Options.AsDynamicHTML.OpenAsFile) {
        try { if ($DynamicHTMLPath -ne '' -and (Test-Path -LiteralPath $DynamicHTMLPath)) { Invoke-Item -LiteralPath $DynamicHTMLPath } } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            $Logger.AddErrorRecord("Error opening file $DynamicHTMLPath.")
        }
    }
    if ($Options.AsExcel.Enabled -and $Options.AsExcel.OpenAsFile) {
        try { if ($ReportFilePathXLSX -ne '' -and (Test-Path -LiteralPath $ReportFilePathXLSX)) { Invoke-Item -LiteralPath $ReportFilePathXLSX } } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            $Logger.AddErrorRecord("Error opening file $ReportFilePathXLSX.")
        }
    }
    if ($Options.AsCSV.Enabled -and $Options.AsCSV.OpenAsFile) {
        foreach ($CSV in $AttachCSV) {
            try { if ($CSV -ne '' -and (Test-Path -LiteralPath $CSV)) { Invoke-Item -LiteralPath $CSV } } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                $Logger.AddErrorRecord("Error opening file $CSV.")
            }
        }
    }
    $AttachedReports = $AttachedReports | Sort-Object -Unique
    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.AddErrorRecord("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 Get-ChoosenDates {
    [CmdletBinding()]
    param([System.Collections.IDictionary] $ReportTimes)
    $Dates = @(if ($ReportTimes.Contains('PastHour') -and $ReportTimes.PastHour.Enabled) {
            $DatesPastHour = Find-DatesPastHour
            if ($DatesPastHour) { $DatesPastHour }
        }
        if ($ReportTimes.Contains('CurrentHour') -and $ReportTimes.CurrentHour.Enabled) {
            $DatesCurrentHour = Find-DatesCurrentHour
            if ($DatesCurrentHour) { $DatesCurrentHour }
        }
        if ($ReportTimes.Contains('PastDay') -and $ReportTimes.PastDay.Enabled) {
            $DatesDayPrevious = Find-DatesDayPrevious
            if ($DatesDayPrevious) { $DatesDayPrevious }
        }
        if ($ReportTimes.Contains('CurrentDay') -and $ReportTimes.CurrentDay.Enabled) {
            $DatesDayToday = Find-DatesDayToday
            if ($DatesDayToday) { $DatesDayToday }
        }
        if ($ReportTimes.Contains('OnDay') -and $ReportTimes.OnDay.Enabled) {
            foreach ($Day in $ReportTimes.OnDay.Days) {
                $DatesReportOnDay = Find-DatesPastWeek $Day
                if ($DatesReportOnDay) { $DatesReportOnDay }
            }
        }
        if ($ReportTimes.Contains('PastMonth') -and $ReportTimes.PastMonth.Enabled) {
            $DatesMonthPrevious = Find-DatesMonthPast -Force $ReportTimes.PastMonth.Force
            if ($DatesMonthPrevious) { $DatesMonthPrevious }
        }
        if ($ReportTimes.Contains('CurrentMonth') -and $ReportTimes.CurrentMonth.Enabled) {
            $DatesMonthCurrent = Find-DatesMonthCurrent
            if ($DatesMonthCurrent) { $DatesMonthCurrent }
        }
        if ($ReportTimes.Contains('PastQuarter') -and $ReportTimes.PastQuarter.Enabled) {
            $DatesQuarterLast = Find-DatesQuarterLast -Force $ReportTimes.PastQuarter.Force
            if ($DatesQuarterLast) { $DatesQuarterLast }
        }
        if ($ReportTimes.Contains('CurrentQuarter') -and $ReportTimes.CurrentQuarter.Enabled) {
            $DatesQuarterCurrent = Find-DatesQuarterCurrent
            if ($DatesQuarterCurrent) { $DatesQuarterCurrent }
        }
        if ($ReportTimes.Contains('CurrentDayMinusDayX') -and $ReportTimes.CurrentDayMinusDayX.Enabled) {
            $DatesCurrentDayMinusDayX = Find-DatesCurrentDayMinusDayX $ReportTimes.CurrentDayMinusDayX.Days
            if ($DatesCurrentDayMinusDayX) { $DatesCurrentDayMinusDayX }
        }
        if ($ReportTimes.Contains('CurrentDayMinuxDaysX') -and $ReportTimes.CurrentDayMinuxDaysX.Enabled) {
            $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX $ReportTimes.CurrentDayMinuxDaysX.Days
            if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX }
        }
        if ($ReportTimes.Contains('CustomDate') -and $ReportTimes.CustomDate.Enabled) {
            $DatesCustom = @{DateFrom = $ReportTimes.CustomDate.DateFrom
                DateTo                = $ReportTimes.CustomDate.DateTo
            }
            if ($DatesCustom) { $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
            }
            $DatesEverything
        }
        if ($ReportTimes.Contains('Last3days') -and $ReportTimes.Last3days.Enabled) {
            $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 3
            if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX }
        }
        if ($ReportTimes.Contains('Last7days') -and $ReportTimes.Last7days.Enabled) {
            $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 7
            if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX }
        }
        if ($ReportTimes.Contains('Last14days') -and $ReportTimes.Last14days.Enabled) {
            $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 14
            if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX }
        })
    return $Dates
}
function Get-DatesDefinitions {
    [CmdletBinding()]
    param([string[]] $Skip)
    $Times = foreach ($Key in $Script:ReportTimes.Keys) { if ($SkipTime -notcontains $Key) { $Key } }
    $Times
}
function Get-EventLogFileList {
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Sections)
    $EventFiles = @(if ($Sections.Contains("Directories")) {
            foreach ($Folder in $Sections.Directories.Keys) {
                $Files = Get-FilesInFolder -Folder $Sections.Directories.$Folder -Extension '*.evtx'
                foreach ($File in $Files) { $File }
            }
        }
        if ($Sections.Contains("Files")) {
            foreach ($FileName in $Sections.Files.Keys) {
                $File = $($Sections.Files.$FileName)
                if ($File -and (Test-Path -LiteralPath $File)) { $File } else { if (-not $Quiet) { $Logger.AddErrorRecord("File $File doesn't exists. Skipping for scan.") } }
            }
        })
    return $EventFiles | Sort-Object -Unique
}
function Get-EventsDefinitions {
    [CmdLetBinding()]
    param([System.Collections.IDictionary] $Definitions)
    [string] $ConfigurationPath = "$Env:ALLUSERSPROFILE\Evotec\PSWinReporting\Definitions"
    try { $Files = Get-ChildItem -LiteralPath $ConfigurationPath -Filter '*.xml' -ErrorAction Stop } catch { $Files = $null }
    $AllDefinitions = $Script:ReportDefinitions
    if ($null -ne $Files) {
        try {
            foreach ($File in $Files) { $AllDefinitions += Import-Clixml -LiteralPath $File.FullName }
            if ($Definitions) { $AllDefinitions += $Definitions }
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            if ($ErrorMessage -like '*Item has already been added. Key in dictionary*') { Write-Warning "Get-EventsDefinitions - Duplicate key in definition. Please make sure names in Hashtables are unique." } else { Write-Warning "Get-EventsDefinitions - Error: $ErrorMessage" }
            $AllDefinitions = $null
        }
    }
    return $AllDefinitions
}
function Get-ServersList {
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Definitions,
        [System.Collections.IDictionary] $Target,
        [System.Collections.IDictionary] $Dates,
        [switch] $Quiet,
        [string] $Who,
        [string] $Whom,
        [string] $NotWho,
        [string] $NotWhom)
    $ServersList = @(if ($Target.Servers.Enabled) {
            if (-not $Quiet) { $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 }
            }
            $Servers
        }
        if ($Target.DomainControllers.Enabled) {
            if (-not $Quiet) { $Logger.AddInfoRecord("Preparing servers list - domain controllers autodetection") }
            [Array] $Servers = (Get-WinADDomainControllers -SkipEmpty).HostName
            $Servers
        })
    if ($Target.LocalFiles.Enabled) {
        if (-not $Quiet) { $Logger.AddInfoRecord("Preparing file list - defined event log files") }
        $Files = Get-EventLogFileList -Sections $Target.LocalFiles
    }
    [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 } } } }
    if ($LogNames.Count -eq 0) {
        $Logger.AddErrorRecord("Definitions provided don't contain any enabled report or subevents within report. Please check your definitions and try again.")
        Exit
    }
    [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 } } } } }
        $NamedDataFilter = @{ }
        if ($Who -ne '') { $NamedDataFilter.SubjectUserName = $Who }
        if ($Whom -ne '') { $NamedDataFilter.TargetUserName = $Whom }
        $NamedDataExcludeFilter = @{ }
        if ($NotWho -ne '') { $NamedDataExcludeFilter.SubjectUserName = $NotWho }
        if ($NotWhom -ne '') { $NamedDataExcludeFilter.TargetUserName = $NotWhom }
        $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
                    NamedDataFilter        = if ($NamedDataFilter.Count -ne 0) { $NamedDataFilter } else { }
                    NamedDataExcludeFilter = if ($NamedDataExcludeFilter.Count -ne 0) { $NamedDataExcludeFilter } else { }
                }
            } 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
                        NamedDataFilter        = if ($NamedDataFilter.Count -ne 0) { $NamedDataFilter } else { }
                        NamedDataExcludeFilter = if ($NamedDataExcludeFilter.Count -ne 0) { $NamedDataExcludeFilter } else { }
                    }
                }
            }
        }
        $OutputFiles = foreach ($File in $FIles) {
            [PSCustomObject]@{Server   = $File
                LogName                = $Log
                EventID                = $EventIDs | Sort-Object -Unique
                Type                   = 'File'
                DateFrom               = $Dates.DateFrom
                DateTo                 = $Dates.DateTo
                NamedDataFilter        = if ($NamedDataFilter.Count -ne 0) { $NamedDataFilter } else { }
                NamedDataExcludeFilter = if ($NamedDataExcludeFilter.Count -ne 0) { $NamedDataExcludeFilter } else { }
            }
        }
        $OutputServers
        $OutputFiles
    }
    if ($ExtendedInput.Count -gt 1) { $ExtendedInput } else { , $ExtendedInput }
}
function Get-ServersListLimited {
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Target,
        [int64] $RecordID,
        [switch] $Quiet,
        [string] $Who,
        [string] $Whom,
        [string] $NotWho,
        [string] $NotWhom)
    if ($Target.Servers.Enabled) {
        if (-not $Quiet) { $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 }
        }
    }
    [Array] $ExtendedInput = foreach ($Server in $Servers) {
        [PSCustomObject] @{Server  = $Server.ComputerName
            LogName                = $Server.LogName
            RecordID               = $RecordID
            NamedDataFilter        = if ($NamedDataFilter.Count -ne 0) { $NamedDataFilter } else { }
            NamedDataExcludeFilter = if ($NamedDataExcludeFilter.Count -ne 0) { $NamedDataExcludeFilter } else { }
        }
    }
    if ($ExtendedInput.Count -gt 1) { $ExtendedInput } else { , $ExtendedInput }
}
function Move-ArchivedLogs {
    [CmdletBinding()]
    param ([string] $ServerName,
        [string] $SourcePath,
        [string] $DestinationPath)
    $NewSourcePath = "\\$ServerName\$($SourcePath.Replace(':\','$\'))"
    $PathExists = Test-Path -LiteralPath $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-TargetServers {
    [CmdLetBinding()]
    param([string[]] $Servers,
        [switch] $UseDC)
    $Target = [ordered]@{Servers = [ordered] @{Enabled = if ($Servers -and ($UseDC -eq $false)) { $true } else { $false }
            Servers                                    = $Servers
        }
        DomainControllers        = [ordered] @{Enabled = $UseDC }
        LocalFiles               = [ordered] @{Enabled = $false
            Directories                                = [ordered] @{ }
            Files                                      = [ordered] @{ }
        }
    }
    return $Target
}
function Protect-ArchivedLogs {
    [CmdletBinding()]
    param ($TableEventLogClearedLogs,
        [string] $DestinationPath)
    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,
        [System.Collections.IDictionary] $LoggerParameters)
    $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")
            Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'ds', $Subscription -LoggerParameters $LoggerParameters
        }
        if ($All -eq $true -and $Subscription -notlike '*PSWinReporting*') {
            $Logger.AddInfoRecord("Deleting own providers - $Subscription")
            Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'ds', $Subscription -LoggerParameters $LoggerParameters
        }
    }
}
function Set-EmailReportDetails($FormattingParameters, $Dates, $Warnings) {
    $DateReport = Get-Date
    $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-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
}
$Script:LoggerParameters = @{ShowTime = $false
    TimeFormat                        = 'yyyy-MM-dd HH:mm:ss'
}
$Script:ProgramWecutil = "wecutil.exe"
$Script:ProgramWevtutil = 'wevtutil.exe'
$Script:ReportDefinitions = [ordered] @{ADUserChanges = @{Enabled = $false
        SqlExport                                                 = @{EnabledGlobal = $false
            Enabled                                                                 = $false
            SqlServer                                                               = 'EVO1'
            SqlDatabase                                                             = 'SSAE18'
            SqlTable                                                                = 'dbo.[EventsNewSpecial]'
            SqlTableCreate                                                          = $true
            SqlTableAlterIfNeeded                                                   = $false
            SqlCheckBeforeInsert                                                    = 'EventRecordID', 'DomainController'
            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'
                'AddedWho'                                   = 'EventAddedWho'
                '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'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            Ignore                                                            = @{SubjectUserName = "ANONYMOUS LOGON" }
            Functions                                                         = @{'ProfilePath' = 'Convert-UAC'
                'OldUacValue'                = 'Remove-WhiteSpace', 'Convert-UAC'
                'NewUacValue'                = 'Remove-WhiteSpace', 'Convert-UAC'
                'UserAccountControl'         = 'Remove-WhiteSpace', 'Split-OnSpace', 'Convert-UAC'
            }
            IgnoreWords                                                       = @{ }
            SortBy                                                            = 'When'
        }
    }
    ADUserChangesDetailed                             = [ordered] @{Enabled = $false
        Events                                                              = @{Enabled = $true
            Events                                          = 5136, 5137, 5139, 5141
            LogName                                         = 'Security'
            Filter                                          = [ordered] @{'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'
                'RecordID'                          = 'Record ID'
                'ID'                                = 'Event ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                          = 'Record ID'
            Descending                                      = $false
            IgnoreWords                                     = @{ }
        }
    }
    ADComputerChangesDetailed                         = [ordered] @{Enabled = $false
        Events                                                              = @{Enabled = $true
            Events                                              = 5136, 5137, 5139, 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'
                'RecordID'                          = 'Record ID'
                'ID'                                = 'Event ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                              = 'Record ID'
            Descending                                          = $false
            IgnoreWords                                         = @{ }
        }
    }
    ADOrganizationalUnitChangesDetailed               = [ordered] @{Enabled = $false
        OUEventsModify                                                      = @{Enabled = $true
            Events                                                        = 5136, 5137, 5139, 5141
            LogName                                                       = 'Security'
            Filter                                                        = [ordered] @{'ObjectClass' = 'organizationalUnit' }
            Functions                                                     = @{'OperationType' = 'ConvertFrom-OperationType' }
            Fields                                                        = [ordered] @{'Computer' = 'Domain Controller'
                'Action'                                    = 'Action'
                'OperationType'                             = 'Action Detail'
                'Who'                                       = 'Who'
                'Date'                                      = 'When'
                'ObjectDN'                                  = 'Organizational Unit'
                'AttributeLDAPDisplayName'                  = 'Field Changed'
                'AttributeValue'                            = 'Field Value'
                'RecordID'                                  = 'Record ID'
                'ID'                                        = 'Event ID'
                'GatheredFrom'                              = 'Gathered From'
                'GatheredLogName'                           = 'Gathered LogName'
            }
            Overwrite                                                     = [ordered] @{'Action Detail#1' = 'Action', 'A directory service object was created.', 'Organizational Unit Created'
                'Action Detail#2'                                  = 'Action', 'A directory service object was deleted.', 'Organizational Unit Deleted'
                'Action Detail#3'                                  = 'Action', 'A directory service object was moved.', 'Organizational Unit Moved'
            }
            OverwriteByField                                              = [ordered] @{'Organizational Unit' = 'Action', 'A directory service object was moved.', 'OldObjectDN'
                'Field Value'                                          = 'Action', 'A directory service object was moved.', 'NewObjectDN'
            }
            SortBy                                                        = 'Record ID'
            Descending                                                    = $false
            IgnoreWords                                                   = @{ }
        }
    }
    ADUserStatus                                      = [ordered] @{Enabled = $false
        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'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                 = 'When'
        }
    }
    ADUserLockouts                                    = [ordered] @{Enabled = $false
        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'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                   = 'When'
        }
    }
    ADUserLogon                                       = [ordered]@{Enabled = $false
        Events                                                             = @{Enabled = $true
            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'
                'VirtualAccount'                    = 'VirtualAccount'
                'ElevatedToken'                     = 'ElevatedToken'
                'LogonType'                         = 'LogonType'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            IgnoreWords                          = @{ }
        }
    }
    ADUserUnlocked                                    = [ordered] @{Enabled = $false
        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'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                   = 'When'
        }
    }
    ADComputerCreatedChanged                          = [ordered] @{Enabled = $false
        Events                                                              = @{Enabled = $true
            Events                                             = 4741, 4742
            LogName                                            = 'Security'
            Ignore                                             = @{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'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            IgnoreWords                                        = @{ }
        }
    }
    ADComputerDeleted                                 = [ordered]@{Enabled = $false
        Events                                                             = @{Enabled = $true
            Events                                     = 4743
            LogName                                    = 'Security'
            IgnoreWords                                = @{ }
            Fields                                     = [ordered] @{'Computer' = 'Domain Controller'
                'Action'                            = 'Action'
                'ObjectAffected'                    = 'Computer Affected'
                'Who'                               = 'Who'
                'Date'                              = 'When'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                     = 'When'
        }
    }
    ADUserLogonKerberos                               = [ordered] @{Enabled = $false
        Events                                                              = @{Enabled = $true
            Events                                        = 4768
            LogName                                       = 'Security'
            IgnoreWords                                   = @{ }
            Functions                                     = [ordered] @{'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'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                        = 'When'
        }
    }
    ADGroupMembershipChanges                          = [ordered]@{Enabled = $false
        Events                                                             = @{Enabled = $true
            Events                                            = 4728, 4729, 4732, 4733, 4746, 4747, 4751, 4752, 4756, 4757, 4761, 4762, 4785, 4786, 4787, 4788
            LogName                                           = 'Security'
            IgnoreWords                                       = @{ }
            Fields                                            = [ordered] @{'Computer' = 'Domain Controller'
                'Action'                            = 'Action'
                'TargetUserName'                    = 'Group Name'
                'MemberNameWithoutCN'               = 'Member Name'
                'Who'                               = 'Who'
                'Date'                              = 'When'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                            = 'When'
        }
    }
    ADGroupEnumeration                                = [ordered] @{Enabled = $false
        Events                                                              = @{Enabled = $true
            Events                                       = 4798, 4799
            LogName                                      = 'Security'
            IgnoreWords                                  = [ordered] @{ }
            Fields                                       = [ordered] @{'Computer' = 'Domain Controller'
                'Action'                            = 'Action'
                'TargetUserName'                    = 'Group Name'
                'Who'                               = 'Who'
                'Date'                              = 'When'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                       = 'When'
        }
    }
    ADGroupChanges                                    = [ordered]@{Enabled = $false
        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'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                  = 'When'
        }
    }
    ADGroupCreateDelete                               = [ordered]@{Enabled = $false
        Events                                                             = @{Enabled = $true
            Events                                       = 4727, 4730, 4731, 4734, 4744, 4748, 4749, 4753, 4754, 4758, 4759, 4763
            LogName                                      = 'Security'
            IgnoreWords                                  = @{ }
            Fields                                       = [ordered] @{'Computer' = 'Domain Controller'
                'Action'                            = 'Action'
                'TargetUserName'                    = 'Group Name'
                'Who'                               = 'Who'
                'Date'                              = 'When'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                       = 'When'
        }
    }
    ADGroupChangesDetailed                            = [ordered] @{Enabled = $false
        Events                                                              = @{Enabled = $true
            Events                                           = 5136, 5137, 5141
            LogName                                          = 'Security'
            Filter                                           = [ordered] @{'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'
                'RecordID'                          = 'Record ID'
                'ID'                                = 'Event ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                           = 'Record ID'
            Descending                                       = $false
            IgnoreWords                                      = @{ }
        }
    }
    ADGroupPolicyChanges                              = [ordered] @{Enabled = $false
        'Group Policy Name Changes'                                         = @{Enabled = $true
            Events                                         = 5136, 5137, 5141
            LogName                                        = 'Security'
            Filter                                         = [ordered] @{'ObjectClass' = 'groupPolicyContainer'
                'AttributeLDAPDisplayName'                                  = $null, 'displayName'
            }
            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'
                'AttributeValue'                                         = 'AttributeValue'
                'OperationType'                                          = 'OperationType'
                'OpCorrelationID'                                        = 'OperationCorelationID'
                'AppCorrelationID'                                       = 'OperationApplicationCorrelationID'
                'DSName'                                                 = 'DSName'
                'DSType'                                                 = 'DSType'
                'Task'                                                   = 'Task'
                'Version'                                                = 'Version'
                '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                                         = [ordered] @{'ObjectClass' = 'groupPolicyContainer'
                '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'
                'AttributeValue'                                  = 'AttributeValue'
                'OperationType'                                   = 'OperationType'
                'OpCorrelationID'                                 = 'OperationCorelationID'
                'AppCorrelationID'                                = 'OperationApplicationCorrelationID'
                'DSName'                                          = 'DSName'
                'DSType'                                          = 'DSType'
                'Task'                                            = 'Task'
                'Version'                                         = 'Version'
                '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                                         = @{'ObjectClass' = 'domainDNS' }
            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'
                'AttributeValue'                                  = 'AttributeValue'
                'OperationType'                                   = 'OperationType'
                'OpCorrelationID'                                 = 'OperationCorelationID'
                'AppCorrelationID'                                = 'OperationApplicationCorrelationID'
                'DSName'                                          = 'DSName'
                'DSType'                                          = 'DSType'
                'Task'                                            = 'Task'
                'Version'                                         = 'Version'
                'ID'                                              = 'Event ID'
                'GatheredFrom'                                    = 'Gathered From'
                'GatheredLogName'                                 = 'Gathered LogName'
            }
            SortBy                                         = 'Record ID'
            Descending                                     = $false
            IgnoreWords                                    = @{ }
        }
    }
    ADLogsClearedSecurity                             = [ordered]@{Enabled = $false
        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'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
                'GatheredLogName'                   = 'Gathered LogName'
            }
            SortBy                                         = 'When'
            IgnoreWords                                    = @{ }
            Overwrite                                      = [ordered] @{'Backup Path' = 'Backup Path', '', 'N/A'
                'Who'                                  = 'Event ID', 1105, 'Automatic Backup'
            }
        }
    }
    ADLogsClearedOther                                = [ordered]@{Enabled = $false
        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'
                'ID'                                = 'Event ID'
                'RecordID'                          = 'Record ID'
                'GatheredFrom'                      = 'Gathered From'
            }
            SortBy                                      = 'When'
            Overwrite                                   = @{'Backup Path' = 'Backup Path', '', 'N/A' }
        }
    }
    NetworkAccessAuthenticationPolicy                 = [ordered]@{Enabled = $false
        Events                                                             = @{Enabled = $true
            Events                                                     = 6272, 6273
            LogName                                                    = 'Security'
            IgnoreWords                                                = @{ }
            Fields                                                     = [ordered] @{'Action' = 'Action'
                'SubjectUserSid'                  = 'SecurityID'
                'Computer'                        = 'Compuer'
                'SubjectUserName'                 = 'AccountName'
                'SubjectDomainName'               = 'Account Domain'
                'CalledStationID'                 = 'CalledStationID'
                'CallingStationID'                = 'CallingStationID'
                'NASIPv4Address'                  = 'NASIPv4Address'
                'NASIPv6Address'                  = 'NASIPv6Address'
                'NASIdentifier'                   = 'NASIdentifier'
                'NASPortType'                     = 'NASPortType'
                'NASPort'                         = 'NASPort'
                'ClientName'                      = 'ClientFriendlyName'
                'ClientIPAddress'                 = 'ClientFriendlyIPAddress'
                'ProxyPolicyName'                 = 'ConnectionRequestPolicyName'
                'NetworkPolicyName'               = 'NetworkPolicyName'
                'AuthenticationProvider'          = 'AuthenticationProvider'
                'AuthenticationServer'            = 'AuthenticationServer'
                'AuthenticationType'              = 'AuthenticationType'
                'EAPType'                         = 'EAPType'
                'Reason'                          = 'Reason'
                'ReasonCode'                      = 'ReasonCode'
                'FullyQualifiedSubjectUserName'   = 'Who'
                'Date'                            = 'When'
                'ID'                              = 'Event ID'
                'RecordID'                        = 'Record ID'
                'GatheredFrom'                    = 'Gathered From'
                'GatheredLogName'                 = 'Gathered LogName'
            }
            SortBy                                                     = 'When'
        }
    }
    "OSCrash"                                         = [ordered]@{Enabled = $false
        Events                                                             = @{Enabled = $true
            Events                             = 6008
            LogName                            = 'System'
            IgnoreWords                        = @{ }
            Fields                             = [ordered] @{"Computer" = "Computer"
                'Date'                              = 'When'
                "MachineName"                       = "ObjectAffected"
                "EventAction"                       = "Action"
                "Message"                           = "ActionDetails"
                "NoNameA1"                          = "ActionDetailsDate"
                "NoNameA0"                          = "ActionDetailsTime"
                "ID"                                = "Event ID"
                "RecordID"                          = "Record ID"
                "GatheredFrom"                      = "Gathered From"
                "GatheredLogName"                   = "Gathered LogName"
            }
            Overwrite                          = @{"Action#1" = "Event ID" , 6008, "System Crash" }
        }
    }
    "OSStartupShutdownCrash"                          = [ordered]@{Enabled = $false
        Events                                                             = [ordered] @{Enabled = $true
            Events                                                      = 12, 13, 41, 4608, 4621, 6008
            LogName                                                     = 'System'
            IgnoreWords                                                 = @{ }
            Filter                                                      = [ordered] @{'ProviderName' = 'Microsoft-Windows-Kernel-General', 'EventLog' }
            FilterOr                                                    = [ordered] @{ }
            Fields                                                      = [ordered] @{"Computer" = "Computer"
                'Date'                                        = 'When'
                "MachineName"                                 = "ObjectAffected"
                "EventAction"                                 = "Action"
                "Message"                                     = "ActionDetails"
                "NoNameA1"                                    = "ActionDetailsDate"
                "NoNameA0"                                    = "ActionDetailsTime"
                "ActionDetailsDateTime"                       = "ActionDetailsDateTime"
                "ID"                                          = "Event ID"
                "RecordID"                                    = "Record ID"
                "GatheredFrom"                                = "Gathered From"
                "GatheredLogName"                             = "Gathered LogName"
            }
            Overwrite                                                   = [ordered] @{"Action#1" = "Event ID", 12, "System Start"
                "Action#2"                                    = "Event ID", 13, "System Shutdown"
                "Action#3"                                    = "Event ID", 41, "System Dirty Reboot"
                "Action#4"                                    = "Event ID", 4608, "Windows is starting up"
                "Action#5"                                    = "Event ID", 4621, "Administrator recovered system from CrashOnAuditFail"
                "Action#6"                                    = "Event ID", 6008, "System Crash"
            }
            OverwriteByField                                            = @{'ActionDetailsDateTime#1#ne' = 'StartTime', $null, 'StartTime'
                'ActionDetailsDateTime#2#ne'                          = '#text', $null, '#text'
            }
        }
    }
    LdapBindingsDetails                               = $LdapBindingsDetails
    LdapBindingsSummary                               = $LdapBindingsSummary
}
$Script:ReportTimes = [ordered] @{PastHour = @{Enabled = $false }
    CurrentHour                            = @{Enabled = $false }
    PastDay                                = @{Enabled = $false }
    CurrentDay                             = @{Enabled = $false }
    OnDay                                  = @{Enabled = $false
        Days                                           = 'Monday'
    }
    PastMonth                              = @{Enabled = $false
        Force                                          = $true
    }
    CurrentMonth                           = @{Enabled = $false }
    PastQuarter                            = @{Enabled = $false
        Force                                          = $true
    }
    CurrentQuarter                         = @{Enabled = $false }
    CurrentDayMinusDayX                    = @{Enabled = $false
        Days                                           = 7
    }
    CurrentDayMinuxDaysX                   = @{Enabled = $false
        Days                                           = 3
    }
    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 Add-ServersToXML {
    [CmdletBinding()]
    param ([string] $FilePath,
        [string[]] $Servers)
    [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)
        [void] $xmlDocument.Subscription.Eventsources.AppendChild($node)
        [void] $xmlDocument.Subscription.Eventsources.EventSource.AppendChild($nodeServer)
    }
    Save-XML -FilePath $FilePath -xml $xmlDocument
}
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 -LoggerParameters $LoggerParameters }
    foreach ($TemplatePath in $ListTemplates) {
        $Logger.AddInfoRecord("Adding provider $TemplatePath to Subscriptions.")
        Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'cs', $TemplatePath -LoggerParameters $LoggerParameters
    }
}
function Add-EventsDefinitions {
    [CmdLetBinding()]
    param([parameter(Mandatory = $true)][System.Collections.IDictionary] $Definitions,
        [parameter(Mandatory = $true)][string] $Name,
        [switch] $Force)
    $AllDefinitions = Get-EventsDefinitions -Definitions $Definitions
    if ($null -ne $AllDefinitions) {
        [string] $ConfigurationPath = "$Env:ALLUSERSPROFILE\Evotec\PSWinReporting\Definitions"
        $null = New-Item -Type Directory -Path $ConfigurationPath -Force
        if (Test-Path -LiteralPath $ConfigurationPath) {
            $XMLPath = "$ConfigurationPath\$Name.xml"
            if ((Test-Path -LiteralPath $XMLPath) -and (-not $Force)) {
                Write-Warning -Message "Definition with name $Name already exists. Please choose another name or use -Force switch."
                return
            }
            $Definitions | Export-Clixml -LiteralPath $XMLPath -Depth 5
        }
    }
}
function Add-WinTaskScheduledForwarder {
    [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 = "$($($(Get-Module -ListAvailable PSWinReportingV2)[0]).ModuleBase)\Templates\Template-ScheduledTask.xml"
    if (Test-Path -LiteralPath $xmlTemplate) {
        $Logger.AddInfoRecord("Found Template $xmlTemplate")
        $ScheduledTaskXML = "$ENV:TEMP\PSWinReportingSchedluledTask.xml"
        Copy-Item -Path $xmlTemplate -Destination $ScheduledTaskXML
        $Logger.AddInfoRecord("Copied template $ScheduledTaskXML")
        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 -LiteralPath $ScheduledTaskXML | Out-String)
        try { $Output = Register-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName -xml $xml -ErrorAction Stop } 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 Find-Events {
    [CmdLetBinding(DefaultParameterSetName = 'Manual')]
    param([parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "Manual", Mandatory = $true)][DateTime] $DateFrom,
        [parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "Manual", Mandatory = $true)][DateTime] $DateTo,
        [parameter(ParameterSetName = "Manual")]
        [parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "DateRange", Mandatory = $false)][alias('Server', 'ComputerName')][string[]] $Servers = $Env:COMPUTERNAME,
        [parameter(ParameterSetName = "Manual")]
        [parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "DateRange", Mandatory = $false)][alias('RunAgainstDC')][switch] $DetectDC,
        [ValidateNotNull()]
        [alias('Credentials')][System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty,
        [parameter(ParameterSetName = "Manual")]
        [parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "DateRange")][switch] $Quiet,
        [parameter(ParameterSetName = "Manual")]
        [parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "DateRange")][System.Collections.IDictionary] $LoggerParameters,
        [parameter(ParameterSetName = "Manual")]
        [parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "DateRange")][switch] $ExtentedOutput,
        [parameter(ParameterSetName = "Manual")]
        [parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "DateRange")][string] $Who,
        [parameter(ParameterSetName = "Manual")]
        [parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "DateRange")][string] $Whom,
        [parameter(ParameterSetName = "Manual")]
        [parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "DateRange")][string] $NotWho,
        [parameter(ParameterSetName = "Manual")]
        [parameter(ParameterSetName = "DateManual")]
        [parameter(ParameterSetName = "DateRange")][string] $NotWhom,
        [parameter(ParameterSetName = "Extended", Mandatory = $true)][System.Collections.IDictionary] $Definitions,
        [parameter(ParameterSetName = "Extended", Mandatory = $true)][System.Collections.IDictionary] $Times,
        [parameter(ParameterSetName = "Extended", Mandatory = $true)][System.Collections.IDictionary] $Target,
        [parameter(ParameterSetName = "Extended", Mandatory = $false)][int] $EventID,
        [parameter(ParameterSetName = "Extended", Mandatory = $false)][int64] $EventRecordID)
    DynamicParam {
        $ParameterSetsAttributesDateManual = New-Object System.Management.Automation.ParameterAttribute
        $ParameterSetsAttributesDateManual.Mandatory = $true
        $ParameterSetsAttributesDateManual.ParameterSetName = 'DateManual'
        $ParamAttribDatesRange = New-Object System.Management.Automation.ParameterAttribute
        $ParamAttribDatesRange.Mandatory = $true
        $ParamAttribDatesRange.ParameterSetName = 'DateRange'
        $ParameterSetsAttributes = New-Object System.Management.Automation.ParameterAttribute
        $ParameterSetsAttributes.Mandatory = $true
        $ParameterSetsAttributes.ParameterSetName = 'Manual'
        $Names = (Get-EventsDefinitions).Keys
        $ReportAttrib = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        $ReportAttrib.Add($ParameterSetsAttributes)
        $ReportAttrib.Add($ParamAttribDatesRange)
        $ReportAttrib.Add($ParameterSetsAttributesDateManual)
        $ReportAttrib.Add((New-Object System.Management.Automation.ValidateSetAttribute($Names)))
        $ReportRuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Report', [string[]], $ReportAttrib)
        $DatesRange = (Get-DatesDefinitions -Skip 'CustomDate', 'CurrentDayMinuxDaysX', 'CurrentDayMinusDayX', 'OnDay')
        $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 {
        foreach ($Report in $Script:ReportDefinitions.Keys) { $Script:ReportDefinitions[$Report].Enabled = $false }
        foreach ($Time in $Script:ReportTimes.Keys) { $Script:ReportTimes[$Time].Enabled = $false }
        $ExecutionTime = Start-TimeLog
        if (-not $LoggerParameters) { $LoggerParameters = $Script:LoggerParameters }
        $Logger = Get-Logger @LoggerParameters
        if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) { $Verbose = $true } else { $Verbose = $false }
        if ($null -ne $Definitions -and $null -ne $Times -and $null -ne $Target) {
            $Dates = Get-ChoosenDates -ReportTimes $Times
            if (-not $Dates) {
                $Logger.AddErrorRecord("Not a single date was choosen for scan. Please fix Times and try again.")
                return
            }
            if ($Dates -is [Array]) {
                $Logger.AddErrorRecord("Currently only 1 date range is supported. Please fix Times and try again.")
                return
            }
            $Reports = foreach ($Report in $Definitions.Keys) { if ($Definitions[$Report].Enabled -eq $true) { $Report } }
        } else {
            $Reports = $PSBoundParameters.Report
            $DatesRange = $PSBoundParameters.DatesRange
            if (-not $Quiet) { $Logger.AddInfoRecord("Preparing reports: $($Reports -join ',')") }
            $Definitions = $Script:ReportDefinitions
            $Times = $Script:ReportTimes
            if ($DatesRange) { $Times.$DatesRange.Enabled = $true } elseif ($DateFrom -and $DateTo) {
                $Times.CustomDate.Enabled = $true
                $Times.CustomDate.DateFrom = $DateFrom
                $Times.CustomDate.DateTo = $DateTo
            } else { return }
            $Dates = Get-ChoosenDates -ReportTimes $Times
            if (-not $Dates) {
                $Logger.AddErrorRecord("Not a single date was choosen for scan. Please fix Times and try again.")
                return
            }
            if ($Dates -is [Array]) {
                $Logger.AddErrorRecord("Currently only 1 date range is supported. Please fix Times and try again")
                return
            }
            foreach ($Report in $Reports) { $Definitions[$Report].Enabled = $true }
            $Target = New-TargetServers -Servers $Servers -UseDC:$DetectDC
        }
        if ($EventRecordID -ne 0 -and $EventID -ne 0) { [Array] $ExtendedInput = Get-ServersListLimited -Target $Target -RecordID $EventRecordID -Quiet:$Quiet -Who $Who -Whom $Whom -NotWho $NotWho -NotWhom $NotWhom } else { [Array] $ExtendedInput = Get-ServersList -Definitions $Definitions -Target $Target -Dates $Dates -Quiet:$Quiet -Who $Who -Whom $Whom -NotWho $NotWho -NotWhom $NotWhom }
        if (-not $ExtendedInput) {
            $Logger.AddErrorRecord("There are no logs/servers to scan. Please fix Targets and try again.")
            return
        }
        foreach ($Entry in $ExtendedInput) { if ($Entry.Type -eq 'Computer') { if (-not $Quiet) { $Logger.AddInfoRecord("Computer $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')") } } else { if (-not $Quiet) { $Logger.AddInfoRecord("File $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')") } } }
        if (-not $Quiet) { $Logger.AddInfoRecord("Getting events for dates $($Dates.DateFrom) to $($Dates.DateTo)") }
        $SplatEvents = @{Verbose = $Verbose
            ExtendedInput        = $ExtendedInput
            ErrorVariable        = 'AllErrors'
            ErrorAction          = 'SilentlyContinue'
        }
        if ($EventRecordID -ne 0 -and $EventId -ne 0) {
            $SplatEvents.RecordID = $EventRecordID
            $SplatEvents.ID = $EventID
        }
        if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $SplatEvents.Credential = $Credential }
        [Array] $AllEvents = Get-Events @SplatEvents
        foreach ($MyError in $AllErrors) { if (-not $Quiet) { $Logger.AddErrorRecord("Server $MyError") } }
        $Elapsed = Stop-TimeLog -Time $ExecutionTime -Option OneLiner
        if (-not $Quiet) { $Logger.AddInfoRecord("Events scanned found $($AllEvents.Count) - Time elapsed: $Elapsed") }
        $Results = Get-EventsOutput -Definitions $Definitions -AllEvents $AllEvents -Quiet:$Quiet
        if ($Results.Count -eq 1) { $Results[$Reports] } else { $Results }
        foreach ($Report in $Script:ReportDefinitions.Keys) { $Script:ReportDefinitions[$Report].Enabled = $false }
        foreach ($Time in $Script:ReportTimes.Keys) { $Script:ReportTimes[$Time].Enabled = $false }
    }
}
function New-WinSubscriptionTemplates {
    [CmdletBinding()]
    param ([string[]] $Servers,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [ValidateScript( { $_ -in (& $SourcesAutoCompleter) })][string[]] $Reports,
        [switch] $AddTemplates,
        [alias('ReportDefinitions')][System.Collections.IDictionary] $Definitions,
        [System.Collections.IDictionary] $Target,
        [System.Collections.IDictionary] $LoggerParameters)
    Begin {
        if (-not $LoggerParameters) { $LoggerParameters = $Script:LoggerParameters }
        $Logger = Get-Logger @LoggerParameters
        if (-not $Reports) { $Reports = (Get-EventsDefinitions).Keys }
        if (-not $Definitions) { $Definitions = $Script:ReportDefinitions }
        foreach ($Report in $Reports) { $Definitions[$Report].Enabled = $true }
        if (-not $Target) {
            $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC
            $Target = [ordered]@{Servers = [ordered] @{Enabled = $true
                    ServerDCs                                  = $ForestInformation.ForestDomainControllers.HostName
                    ServerOther                                = $Servers
                }
            }
        }
    }
    Process {
        [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 ', ')") } }
        $xmlTemplate = "$($($(Get-Module -ListAvailable PSWinReportingV2)[0]).ModuleBase)\Templates\Template-Collector.xml"
        if (Test-Path -LiteralPath $xmlTemplate) {
            $Logger.AddInfoRecord("Found Template $xmlTemplate")
            $SubscriptionCount = 0
            $InputServers = ($ExtendedInput | Group-Object -Property LogName)
            $ListTemplates = foreach ($InputData in $InputServers) {
                $Servers = $InputData.Group.Server
                $EventID = $InputData.Group.EventID | Select-Object -Unique
                $LogName = $InputData.Name
                $SplitArrayID = Split-Array -inArray $EventID -size 22
                $Array = foreach ($ID in $SplitArrayID) { Get-EventsFilter -ID $ID -LogName $LogName }
                foreach ($Events in $Array) {
                    $SubscriptionCount++
                    $SubscriptionTemplate = "$ENV:TEMP\PSWinReportingSubscription$SubscriptionCount.xml"
                    Copy-Item -Path $xmlTemplate -Destination $SubscriptionTemplate
                    $Logger.AddInfoRecord("Copied template $SubscriptionTemplate")
                    Add-ServersToXML -FilePath $SubscriptionTemplate -Servers $Servers
                    Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'SubscriptionId' -Value "PSWinReporting Subscription Events - $SubscriptionCount"
                    Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'ContentFormat' -Value 'Events'
                    Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'ConfigurationMode' -Value 'Custom'
                    Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'Query' -Value $Events
                    $SubscriptionTemplate
                }
            }
        } else { $Logger.AddInfoRecord("Template not found $xmlTemplate") }
        if ($AddTemplates) { Set-SubscriptionTemplates -ListTemplates $ListTemplates -DeleteOwn -LoggerParameters $LoggerParameters }
    }
    End { foreach ($Report in $Script:ReportDefinitions.Keys) { $Script:ReportDefinitions[$Report].Enabled = $false } }
}
[scriptblock] $SourcesAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $Reports = (Get-EventsDefinitions).Keys
    $Reports | Sort-Object }
Register-ArgumentCompleter -CommandName New-WinSubscriptionTemplates -ParameterName Reports -ScriptBlock $SourcesAutoCompleter
function Remove-WinTaskScheduledForwarder {
    [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") }
        }
    }
}
function Start-WinNotifications {
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Options,
        [System.Collections.IDictionary] $Definitions,
        [System.Collections.IDictionary] $Target,
        [int] $EventID,
        [int64] $EventRecordID,
        [string] $EventChannel)
    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) { 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
    foreach ($Report in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled' }) {
        if ($Definitions.$Report.Enabled) {
            $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 = Get-EventsTranslation -Events $AllEvents -EventsDefinition $Definitions.$Report.$SubReport -EventIDs $EventsNeeded -EventsType $EventsType
                    $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) {
                    [Array] $MyValue = Find-EventsTo -Prioritize -Events $Results.$ReportName -DataSet $Definitions.$ReportName.Priority.$Priority
                    if ($MyValue.Count) {
                        $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-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)
    if ($Options.Logging) { $LoggerParameters = $Options.Logging } else { $LoggerParameters = $Script:LoggerParameters }
    $Logger = Get-Logger @LoggerParameters
    $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 Start-WinSubscriptionService {
    [CmdletBinding()]
    param([System.Collections.IDictionary] $LoggerParameters)
    if (-not $LoggerParameters) { $LoggerParameters = $Script:LoggerParameters }
    $Logger = Get-Logger @LoggerParameters
    $Logger.AddInfoRecord('Starting Windows Event Collector service.')
    $Output = Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'qc', '/q:true'
    $Logger.AddInfoRecord($Output)
}
Export-ModuleMember -Function @('Add-EventsDefinitions', 'Add-WinTaskScheduledForwarder', 'Find-Events', 'New-WinSubscriptionTemplates', 'Remove-WinTaskScheduledForwarder', 'Start-WinNotifications', 'Start-WinReporting', 'Start-WinSubscriptionService') -Alias @()