Testimo.psm1
function Get-DomainTrusts { [CmdletBinding()] param([string] $Domain) try { $ADModule = Import-Module PSWinDocumentation.AD -PassThru try { $Trusts = & $ADModule { Get-WinADDomainTrusts -WarningAction SilentlyContinue -Domain $Domain } $Domain } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " } if ($Trusts.Count -gt 0) { [ordered] @{Status = $true; Output = $Trusts; Extended = "" } } else { [ordered] @{Status = $false; Output = $Trusts; Extended = $ErrorMessage } } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " [ordered] @{Status = $false; Output = @(); Extended = $ErrorMessage } } } function Get-ForestOptionalFeatures { [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) if ($Replication.Status -eq $true) { [ordered] @{Status = $true; Output = ''; Extended = $Replication.StatusMessage } } else { [ordered] @{Status = $false; Output = ''; Extended = $Replication.StatusMessage } } } 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, [nullable[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) { if ($Output.Output) { foreach ($_ in $Output.Output) { $_ } } else { foreach ($_ in $Output) { $_ } } } if ($null -eq $ExpectedStatus) { Write-Color -Text ' [', 'Informative', ']' -Color Cyan, Magenta, Cyan } elseif ($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, Cyan, Green, Cyan -StartSpaces ($Level * 6) } else { Write-Color -Text '[t] ', $_.TestName, ' [', 'Fail', ']', " [", $Value, "]" -Color Cyan, Red, Cyan, Red, Cyan, 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, 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, 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() $ADModule = Import-Module PSWinDocumentation.AD -PassThru $Forest = Start-TestProcessing -Test 'Forest Information - Is Available' -ExpectedStatus $true -OutputRequired { Get-WinADForest } Start-TestProcessing -Test "Testing optional features" -Level 1 -Data { Get-ForestOptionalFeatures } -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 "Testing LDAP Connectivity" -Level 1 -Data { Test-LDAP -ComputerName $_.HostName } -Tests { Test-Array -TestName "Domain Controller - $($_.HostName) | LDAP Port is Available" -Property 'LDAP' -ExpectedValue $true -SearchObjectValue $_.HostName -SearchObjectProperty 'ComputerFQDN' Test-Array -TestName "Domain Controller - $($_.HostName) | LDAP SSL Port is Available" -Property 'LDAPS' -ExpectedValue $true -SearchObjectValue $_.HostName -SearchObjectProperty 'ComputerFQDN' Test-Array -TestName "Domain Controller - $($_.HostName) | LDAP GC Port is Available" -Property 'GlobalCatalogLDAP' -ExpectedValue $true -SearchObjectValue $_.HostName -SearchObjectProperty 'ComputerFQDN' Test-Array -TestName "Domain Controller - $($_.HostName) | LDAP GC SSL Port is Available" -Property 'GlobalCatalogLDAPS' -ExpectedValue $true -SearchObjectValue $_.HostName -SearchObjectProperty 'ComputerFQDN' } -Simple 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 Status" -SearchObjectProperty 'Name' -SearchObjectValue $Service -Property 'Status' -ExpectedValue 'Running' Test-Array -TestName "Domain Controller - $($_.HostName) | Service $Service Start Type" -SearchObjectProperty 'Name' -SearchObjectValue $Service -Property 'StartType' -ExpectedValue 'Automatic' } } -Simple Start-TestProcessing -Test "Domain Controller - $($_.HostName) | Responds to PowerShell Queries" -ExpectedStatus $true -IsTest -Level 1 { Get-WinADDomain -Domain $_ } } $Trusts = Start-TestProcessing -Test "Testing Trusts Availability" -Level 1 -OutputRequired { & $ADModule { Get-WinADDomainTrusts -Domain $Domain } $Domain } foreach ($_ in $Trusts) { Test-Value -TestName "Trusts Verification | Source $Domain, Target $($_.'Trust Target'), Direction $($_.'Trust Direction')" -Level 2 -Object $_ -Property 'Trust Status' -ExpectedValue 'OK' } $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 $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([Object] $Object, [string] $TestName, [string] $Property, [Object] $ExpectedValue, [int] $Level) if ($Object) { Write-Color '[i] ', $TestName -Color Cyan, Yellow, Cyan -NoNewLine -StartSpaces ($Level * 3) if ($Object.$Property -ne $ExpectedValue) { Write-Color -Text ' [', 'Fail', ']' -Color Cyan, Red, Cyan } else { Write-Color -Text ' [', 'Pass', ']' -Color Cyan, Green, Cyan } } else { [PSCustomObject] @{TestName = $TestName Property = $Property ExpectedValue = $ExpectedValue Type = 'Hash' } } } Export-ModuleMember -Function @('Test-ImoAD') -Alias @() |