Testimo.psm1

function Get-TestForestOptionalFeatures {
    [CmdletBinding()]
    param()
    try {
        $ADModule = Import-Module PSWinDocumentation.AD -PassThru
        try { $OptionalFeatures = & $ADModule { Get-WinADForestOptionalFeatures -WarningAction SilentlyContinue } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " }
        if ($OptionalFeatures.Count -gt 0) { [ordered] @{Status = $true; Output = $OptionalFeatures; Extended = "" }
        } else { [ordered] @{Status = $false; Output = $OptionalFeatures; Extended = $ErrorMessage }
        }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        [ordered] @{Status = $false; Output = @(); Extended = $ErrorMessage }
    }
}
function Get-WinADDC {
    [CmdletBinding()]
    param([string] $Domain = $Env:USERDNSDOMAIN)
    try {
        $Output = Get-ADDomainController -Server $Domain -Filter * -ErrorAction Stop
        [ordered] @{Status = $true; Output = $Output; Extended = 'No error.' }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        [ordered] @{Status = $false; Output = @(); Extended = $ErrorMessage }
    }
}
function Get-WinADDomain {
    [CmdletBinding()]
    param([string] $Domain)
    @(try {
            $Output = Get-ADDomain -Server $Domain -ErrorAction Stop
            [ordered] @{Status = $true; Output = $Output; Error = '' }
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            [ordered] @{Status = $false; Output = @(); Error = $ErrorMessage }
        })
}
function Get-WinADForest {
    [CmdletBinding()]
    param()
    try {
        $Output = Get-ADForest -ErrorAction Stop
        [ordered] @{Status = $true; Output = $Output; Extended = 'No error.' }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        [ordered] @{Status = $false; Output = @(); Extended = $ErrorMessage }
    }
}
function Get-WinTestConnection {
    [CmdletBinding()]
    param([string] $Computer)
    try {
        $Output = Test-NetConnection -ComputerName $Computer -WarningAction SilentlyContinue
        if ($Output.PingSucceeded) { [ordered] @{Status = $true; Output = $Output; Extended = "Ping Replay Details (RTT): $($Output.PingReplyDetails.RoundtripTime)" }
        } else { [ordered] @{Status = $false; Output = $Output; Extended = "$($Output.PingReplyDetails.Status) - $($Output.PingReplyDetails.Address)" }
        }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        [ordered] @{Status = $false; Output = @(); Extended = $ErrorMessage }
    }
}
function Get-WinTestConnectionPort {
    [CmdletBinding()]
    param([string] $Computer,
        [int] $Port)
    try {
        $Output = Test-NetConnection -ComputerName $Computer -WarningAction SilentlyContinue -Port $Port
        if ($Output.TcpTestSucceeded) { [ordered] @{Status = $true; Output = $Output; Extended = "Port available for connection." }
        } else { [ordered] @{Status = $false; Output = $Output; Extended = "$($Output.PingReplyDetails.Status) - $($Output.PingReplyDetails.Address)" }
        }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        [ordered] @{Status = $false; Output = @(); Extended = $ErrorMessage }
    }
}
function Get-WinTestIsLapsAvailable {
    [CmdletBinding()]
    param()
    $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    $ComputerProperties = & $ADModule { Get-WinADForestSchemaPropertiesComputers }
    if ($ComputerProperties.Name -contains 'ms-Mcs-AdmPwd') { [ordered] @{Status = $true; Output = ''; Extended = "LAPS schema properties are available." }
    } else { [ordered] @{Status = $false; Output = ''; Extended = "LAPS schema properties are missing." }
    }
}
$Script:Tests = @{ForestInformation = @{ }
    DomainControllers = @{ }
    DomainControllersPing = @{ }
    DomainControllersPort53 = @{ }
    DomainControllersServiceDNSServer = @{ }
    DomainControllersServiceActiveDirectoryDomainServices = @{ }
    DomainControllersServiceActiveDirectoryWebServices = @{ }
    DomainControllersServiceKerberosKeyDistributionCenter = @{ }
    DomainControllersServiceNetlogon = @{ }
    LAPS = @{ }
}
function Get-WinTestReplication {
    [CmdletBinding()]
    param([bool] $Status)
    try {
        $Replication = Get-WinADForestReplication -WarningAction SilentlyContinue
        if ($Replication.Status -contains (-not $Status)) { [ordered] @{Status = $false; Output = $Replication; Extended = "There were some errors." }
        } else { [ordered] @{Status = $true; Output = $Replication; Extended = "No errors." }
        }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        [ordered] @{Status = $false; Output = @(); Extended = $ErrorMessage }
    }
}
function Get-WinTestReplicationSingular {
    [CmdletBinding()]
    param([PSCustomObject] $Replication)
    try { if ($Replication.Status -eq $true) { [ordered] @{Status = $true; Output = ''; Extended = $Replication.StatusMessage } } else { [ordered] @{Status = $false; Output = ''; Extended = $Replication.StatusMessage }
        }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        [ordered] @{Status = $false; Output = @(); Extended = $ErrorMessage }
    }
}
function Get-WinTestService {
    [CmdletBinding()]
    param([string] $Computer,
        [string] $Service,
        [string] $Status = 'Running')
    try {
        $DNSsvc = Get-Service -ComputerName $Computer -DisplayName 'DNS Server' -ErrorAction Stop
        if ($DNSsvc.Status -eq $Status) { [ordered] @{Status = $true; Output = $Output; Extended = "Status is $Status" }
        } else { [ordered] @{Status = $false; Output = $Output; Extended = "$($DNSsvc.Status)" }
        }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        [ordered] @{Status = $false; Output = @(); Extended = $ErrorMessage }
    }
}
function Start-TestProcessing {
    [CmdletBinding()]
    param([ScriptBlock] $Execute,
        [ScriptBlock] $Data,
        [ScriptBlock] $Tests,
        [string] $Test,
        [switch] $OutputRequired,
        [bool] $ExpectedStatus,
        [int] $Level = 0,
        [switch] $IsTest,
        [switch] $Simple)
    if ($Execute) {
        if ($IsTest) { Write-Color '[i] ', $Test -Color Cyan, Yellow, Cyan -NoNewLine -StartSpaces ($Level * 3) } else { Write-Color '[i] ', $Test -Color Cyan, Yellow, Cyan -NoNewLine -StartSpaces ($Level * 3) }
        [Array] $Output = & $Execute
        if ($OutputRequired) { foreach ($_ in $Output.Output) { $_ } }
        if ($ExpectedStatus -eq $Output.Status) { if ($Output.Extended) { Write-Color -Text ' [', 'Pass', ']', " [", $Output.Extended, "]" -Color Cyan, Green, Cyan, Cyan, Green, Cyan } else { Write-Color -Text ' [', 'Pass', ']' -Color Cyan, Green, Cyan } } else { if ($Output.Extended) { Write-Color -Text ' [', 'Fail', ']', " [", $Output.Extended, "]" -Color Cyan, Red, Cyan, Cyan, Red, Cyan } else { Write-Color -Text ' [', 'Fail', ']' -Color Cyan, Red, Cyan } }
        $Script:TestResults.Add([PSCustomObject]@{Test = $Test
                Status = $ExpectedStatus -eq $Output.Status
                Extended = $Output.Extended
            })
    }
    if ($Data) {
        Write-Color '[i] ', $Test -Color Yellow, DarkGray, White -StartSpaces ($Level * 3) -NoNewLine
        [Array] $OutputData = & $Data
        if (-not $Simple) {
            $GatheredData = $OutputData.Output
            if ($Output.Output -and $OutputData.Status -eq $false) { if ($OutputData.Extended) { Write-Color -Text ' [', 'Fail', ']', " [", $Output.Extended, "]" -Color Cyan, Red, Cyan, Cyan, Red, Cyan } else { Write-Color -Text ' [', 'Fail', ']' -Color Cyan, Red, Cyan } } else { if ($OutputData.Extended) { Write-Color -Text ' [', 'Pass', ']', " [", $Output.Extended, "]" -Color Cyan, Green, Cyan, Cyan, Green, Cyan } else { Write-Color -Text ' [', 'Pass', ']' -Color Cyan, Green, Cyan } }
        } else {
            try {
                $GatheredData = $OutputData
                Write-Color -Text ' [', 'Pass', ']' -Color Cyan, Green, Cyan
            } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                Write-Color -Text ' [', 'Fail', ']', " [", $ErrorMessage, "]" -Color Cyan, Red, Cyan, Cyan, Red, Cyan
            }
        }
        [Array] $TestsExecution = & $Tests
        foreach ($_ in $TestsExecution) {
            if ($_.Type -eq 'Hash') {
                $Value = $GatheredData.$($_.Property)
                if ($Value -eq $_.ExpectedValue) { Write-Color -Text '[t] ', $_.TestName, ' [', 'Passed', ']', " [", $Value, "]" -Color Cyan, Green, Cyan, Green, Cyan -StartSpaces ($Level * 6) } else { Write-Color -Text '[t] ', $_.TestName, ' [', 'Fail', ']', " [", $Value, "]" -Color Cyan, Red, Cyan, Red, Cyan -StartSpaces ($Level * 6) }
            } elseif ($_.Type -eq 'Array') {
                foreach ($Object in $GatheredData) {
                    if ($Object.$($_.SearchObjectProperty) -eq $_.SearchObjectValue) {
                        $Value = $Object.$($_.Property)
                        if ($Value -eq $_.ExpectedValue) {
                            Write-Color -Text '[t] ', $_.TestName, ' [', 'Passed', ']', " [", $Value, "]" -Color Cyan, Yellow, Cyan, Green, Cyan, Green, Cyan -StartSpaces ($Level * 6)
                            $Script:TestResults.Add([PSCustomObject]@{Test = $_.TestName
                                    Status = $True
                                    Extended = $Value
                                })
                        } else {
                            Write-Color -Text '[t] ', $_.TestName, ' [', 'Fail', ']', " [", $Value, "]" -Color Cyan, Red, Cyan, Red, Cyan, Red, Cyan -StartSpaces ($Level * 6)
                            $Script:TestResults.Add([PSCustomObject]@{Test = $_.TestName
                                    Status = $False
                                    Extended = $Value
                                })
                        }
                    }
                }
            }
        }
    }
}
function Test-Array {
    [CmdletBinding()]
    param([string] $TestName,
        [string] $SearchObjectProperty,
        [string] $SearchObjectValue,
        [string] $Property,
        [Object] $ExpectedValue)
    [PSCustomObject] @{TestName = $TestName
        SearchObjectProperty = $SearchObjectProperty
        SearchObjectValue = $SearchObjectValue
        Property = $Property
        ExpectedValue = $ExpectedValue
        Type = 'Array'
    }
}
function Test-ImoAD {
    [CmdletBinding()]
    param([switch] $ReturnResults)
    $Time = Start-TimeLog
    $Script:TestResults = [System.Collections.Generic.List[PSCustomObject]]::new()
    $Forest = Start-TestProcessing -Test 'Forest Information - Is Available' -ExpectedStatus $true -OutputRequired { Get-WinADForest }
    Start-TestProcessing -Test "Testing optional features" -Level 1 -Data { Get-TestForestOptionalFeatures } -Tests { Test-Value -TestName 'Is Recycle Bin Enabled?' -Property 'Recycle Bin Enabled' -ExpectedValue $true
        Test-Value -TestName 'is Laps Enabled?' -Property 'Laps Enabled' -ExpectedValue $true }
    foreach ($Domain in $Forest.Domains) {
        $DomainInformation = Start-TestProcessing -Test "Domain $Domain - Is Available" -ExpectedStatus $true -OutputRequired -IsTest { Get-WinADDomain -Domain $Domain }
        $DomainControllers = Start-TestProcessing -Test "Domain Controllers - List is Available" -ExpectedStatus $true -OutputRequired -Level 1 { Get-WinADDC -Domain $Domain }
        foreach ($_ in $DomainControllers) {
            Start-TestProcessing -Test "Domain Controller - $($_.HostName) | Connectivity Ping $($_.HostName)" -Level 1 -ExpectedStatus $true -IsTest { Get-WinTestConnection -Computer $_.HostName }
            Start-TestProcessing -Test "Domain Controller - $($_.HostName) | Connectivity Port 53 (DNS)" -Level 1 -ExpectedStatus $true -IsTest { Get-WinTestConnectionPort -Computer $_.HostName -Port 53 }
            $Services = @('ADWS',
                'DNS',
                'DFS',
                'DFSR',
                'Eventlog',
                'EventSystem',
                'KDC',
                'LanManWorkstation',
                'LanManServer',
                'NetLogon',
                'NTDS',
                'RPCSS',
                'SAMSS',
                'W32Time')
            Start-TestProcessing -Test "Testing Services - Domain Controller - $($_.HostName)" -Level 1 -Data { Get-PSService -Computers $_ -Services $Services } -Tests { foreach ($Service in $Services) { Test-Array -TestName "Domain Controller - $($_.HostName) | Service $Service" -SearchObjectProperty 'Name' -SearchObjectValue $Service -Property 'Status' -ExpectedValue 'Running' } } -Simple
        }
    }
    $Replication = Start-TestProcessing -Test "Forest Replication" -Level 1 -ExpectedStatus $true -OutputRequired { Get-WinTestReplication -Status $true }
    foreach ($_ in $Replication) { Start-TestProcessing -Test "Replication from $($_.Server) to $($_.ServerPartner)" -Level 2 -ExpectedStatus $true -IsTest { Get-WinTestReplicationSingular -Replication $_ } }
    $TestsPassed = (($Script:TestResults) | Where-Object { $_.Status -eq $true }).Count
    $TestsFailed = (($Script:TestResults) | Where-Object { $_.Status -eq $false }).Count
    $TestsSkipped = 0
    $TestsInformational = 0
    $EndTime = Stop-TimeLog -Time $Time -Option OneLiner
    Write-Color -Text '[i] ', 'Time to execute tests: ', $EndTime -Color Yellow, DarkGray, Cyan
    Write-Color -Text '[i] ', 'Tests Passed: ', $TestsPassed, ' Tests Failed: ', $TestsFailed, ' Tests Skipped: ', $TestsSkipped -Color Yellow, DarkGray, Green, DarkGray, Red, DarkGray, Cyan
    if ($ReturnResults) { $Script:TestResults }
}
function Test-Value {
    [CmdletBinding()]
    param([string] $TestName,
        [string] $Property,
        [Object] $ExpectedValue)
    [PSCustomObject] @{TestName = $TestName
        Property = $Property
        ExpectedValue = $ExpectedValue
        Type = 'Hash'
    }
}
Export-ModuleMember -Function @('Test-ImoAD') -Alias @()