Testimo.psm1
$Script:SBComputerOperatingSystem = { Get-ComputerOperatingSystem -ComputerName $DomainController -WarningAction SilentlyContinue } $Script:SBDomainControllersDFSR = { Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\DFSR\Parameters" -ComputerName $DomainController } $Script:SBDomainControllersDiskSpace = { Get-ComputerDiskLogical -ComputerName $DomainController -OnlyLocalDisk -WarningAction SilentlyContinue } $Script:SBDomainControllersFirewall = { Get-ComputerNetwork -ComputerName $DomainController } $Script:SBDomainControllersLDAP = { Test-LDAP -ComputerName $DomainController -WarningAction SilentlyContinue } $Script:SBDomainControllersPing = { Test-NetConnection -ComputerName $DomainController -WarningAction SilentlyContinue } $Script:SBDomainControllersServices = { $Services = @('ADWS', 'DNS', 'DFS', 'DFSR', 'Eventlog', 'EventSystem', 'KDC', 'LanManWorkstation', 'LanManServer', 'NetLogon', 'NTDS', 'RPCSS', 'SAMSS', 'Spooler', 'W32Time') Get-PSService -Computers $DomainController -Services $Services } $Script:SBDomainControllersSMB = { Get-ComputerSMB -ComputerName $DomainController } $Script:SBDomainControllersTimeSettings = { Get-TimeSetttings -ComputerName $DomainController -Domain $Domain } $Script:SBDomainDNSForwaders = { $PSWinDocumentationDNS = Import-Module PSWinDocumentation.DNS -PassThru & $PSWinDocumentationDNS { param($Domain) $Forwarders = Get-WinDnsServerForwarder -Domain $Domain -WarningAction SilentlyContinue Compare-MultipleObjects -Objects $Forwarders -FormatOutput -CompareSorted:$true -ExcludeProperty GatheredFrom -SkipProperties -Property 'IpAddress' } $Domain } $Script:SBDomainDNSScavenging = { $PSWinDocumentationDNS = Import-Module PSWinDocumentation.DNS -PassThru & $PSWinDocumentationDNS { param($Domain) $Object = Get-WinDnsServerScavenging -Domain $Domain $Object | Where-Object { $_.ScavengingInterval -ne 0 -and $null -ne $_.ScavengingInterval } } $Domain } $Script:SBDomainDnsZones = { $PSWinDocumentationDNS = Import-Module PSWinDocumentation.DNS -PassThru & $PSWinDocumentationDNS { param($Domain) $Zones = Get-WinDnsServerZones -ZoneName $Domain -Domain $Domain Compare-MultipleObjects -Objects $Zones -FormatOutput -CompareSorted:$true -ExcludeProperty GatheredFrom -SkipProperties -Property 'AgingEnabled' } $Domain } $Script:SBDomainEmptyOrganizationalUnits = { param($Domain) $OrganizationalUnits = Get-ADOrganizationalUnit -Filter * -Properties distinguishedname -Server $Domain | Select-Object -ExpandProperty distinguishedname $AllUsedOU = Get-ADObject -Filter "ObjectClass -eq 'user' -or ObjectClass -eq 'computer' -or ObjectClass -eq 'group'" -Server $Domain | Where-Object { ($_.DistinguishedName -notlike '*LostAndFound*') -and ($_.DistinguishedName -match 'OU=(.*)') } | ForEach-Object { $matches[0] } | Select-Object -Unique $OrganizationalUnits | Where-Object { ($AllUsedOU -notcontains $_) -and -not (Get-ADOrganizationalUnit -Filter * -SearchBase $_ -SearchScope 1 -Server $Domain) } } $Script:SBDomainOrphanedFSP = { param($Domain) $AllFSP = Get-WinADUsersFP -Domain $Domain $OrphanedObjects = $AllFSP | Where-Object { $_.TranslatedName -eq $null } $OrphanedObjects } $Script:SBDomainPasswordComplexity = { $ADModule = Import-Module PSWinDocumentation.AD -PassThru & $ADModule { param($Domain); Get-WinADDomainDefaultPasswordPolicy -Domain $Domain } $Domain } function Get-ForestDFSHealth { param([string[]] $Domains, [int] $EventDays = 1) $Forest = Get-ADForest if (-not $Domains) { $Domains = $Forest.Domains } [Array] $Table = foreach ($Domain in $Domains) { $DomainControllers = Get-ADDomainController -Filter * -Server $Domain [Array]$GPOs = @(Get-GPO -All -Domain $Domain) foreach ($DC in $DomainControllers) { $DCName = $DC.Name $Hostname = $DC.Hostname $DN = $DC.ComputerObjectDN $LocalSettings = "CN=DFSR-LocalSettings,$DN" $Subscriber = "CN=Domain System Volume,$LocalSettings" $Subscription = "CN=SYSVOL Subscription,$Subscriber" $DomainSummary = [ordered] @{"DC" = $DCName "IsPDC" = $DC.OperationMasterRoles -contains 'PDCEmulator' "Domain" = $Domain "GPOCount" = $GPOs.Count "SysvolCount" = 0 "Availability" = $false "Member(CN=Topology)" = $null "DFSErrors" = 0 "DFSEvents" = $null "DFSLocalSetting" = '' "DomainSystemVolume" = '' "SYSVOLSubscription" = '' } try { $MemberReference = (Get-ADObject $Subscriber -Properties msDFSR-MemberReference -Server $Domain -ErrorAction Stop).'msDFSR-MemberReference' -like "CN=$DCName,*" if ($MemberReference) { $DomainSummary['Member(CN=Topology)'] = $true } } catch { $DomainSummary['Member(CN=Topology)'] = $false } try { $DFSLocalSetting = Get-ADObject $LocalSettings -Server $Domain -ErrorAction Stop if ($DFSLocalSetting) { $DomainSummary['DFSLocalSetting'] = $true } } catch { $DomainSummary['DFSLocalSetting'] = $false } try { $DomainSystemVolume = Get-ADObject $Subscriber -Server $Domain -ErrorAction Stop if ($DomainSystemVolume) { $DomainSummary['DomainSystemVolume'] = $true } } catch { $DomainSummary['DomainSystemVolume'] = $false } try { $SysVolSubscription = Get-ADObject $Subscription -Server $Domain -ErrorAction Stop if ($SysVolSubscription) { $DomainSummary['SYSVOLSubscription'] = $true } } catch { $DomainSummary['SYSVOLSubscription'] = $false } try { $SYSVOL = Get-ChildItem -Path "\\$Hostname\SYSVOL\$Domain\Policies" -ErrorAction Stop $DomainSummary['SysvolCount'] = $SYSVOL.Count } catch { $DomainSummary['SysvolCount'] = 0 } if (Test-Connection $Hostname -ErrorAction SilentlyContinue) { $DomainSummary['Availability'] = $true } else { $DomainSummary['Availability'] = $false } try { $Yesterday = (Get-Date).AddDays($EventDays) [Array] $Events = Get-WinEvent -LogName "DFS Replication" -ComputerName $Hostname | Where-Object { ($_.LevelDisplayName -eq "Error") -and ($_.TimeCreated -ge $Yesterday) } $DomainSummary['DFSErrors'] = $Events.Count $DomainSummary['DFSEvents'] = $Events } catch { $DomainSummary['DFSErrors'] = $null } } [PSCustomObject] $DomainSummary } $Table } $Script:SBSysvolMode = { $DistinguishedName = (Get-ADDomain -Server $Domain).DistinguishedName $Object = "CN=DFSR-GlobalSettings,CN=System,$DistinguishedName" $Object = Get-ADObject -Identity $ADObj -Properties * -Server $Domain if ($Object.'msDFSR-Flags' -gt 47) { [PSCustomObject] @{'SysvolMode' = 'DFS-R' } } else { [PSCustomObject] @{'SysvolMode' = 'Not DFS-R' } } } $Script:SBDomainTimeSynchronizationInternal = { Get-ComputerTime -TimeTarget $DomainController -WarningAction SilentlyContinue } $Script:SBDomainTimeSynchronizationExternal = { Get-ComputerTime -TimeTarget $DomainController -TimeSource 'pool.ntp.org' -WarningAction SilentlyContinue } $Script:SBDomainTombstoneLifetime = { $Output = (Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$((Get-ADRootDSE).configurationNamingContext)" -Properties tombstoneLifetime).tombstoneLifetime if ($null -eq $Output) { [PSCustomObject] @{TombstoneLifeTime = 60 } } else { [PSCustomObject] @{TombstoneLifeTime = $Output } } } $script:SBDomainWellKnownFolders = { param($Domain) $DomainInformation = Get-ADDomain -Server $Domain $WellKnownFolders = $DomainInformation | Select-Object -Property UsersContainer, ComputersContainer, DomainControllersContainer, DeletedObjectsContainer, SystemsContainer, LostAndFoundContainer, QuotasContainer, ForeignSecurityPrincipalsContainer $CurrentWellKnownFolders = [ordered] @{ } $DomainDistinguishedName = $DomainInformation.DistinguishedName $DefaultWellKnownFolders = [ordered] @{UsersContainer = "CN=Users,$DomainDistinguishedName" ComputersContainer = "CN=Computers,$DomainDistinguishedName" DomainControllersContainer = "OU=Domain Controllers,$DomainDistinguishedName" DeletedObjectsContainer = "CN=Deleted Objects,$DomainDistinguishedName" SystemsContainer = "CN=System,$DomainDistinguishedName" LostAndFoundContainer = "CN=LostAndFound,$DomainDistinguishedName" QuotasContainer = "CN=NTDS Quotas,$DomainDistinguishedName" ForeignSecurityPrincipalsContainer = "CN=ForeignSecurityPrincipals,$DomainDistinguishedName" } foreach ($_ in $WellKnownFolders.PSObject.Properties.Name) { $CurrentWellKnownFolders[$_] = $DomainInformation.$_ $CurrentWellKnownFolders[$_] = $DomainInformation.$_ } Compare-MultipleObjects -Object @($DefaultWellKnownFolders, $CurrentWellKnownFolders) -SkipProperties } $Script:SBForestOptionalFeatures = { $ADModule = Import-Module PSWinDocumentation.AD -PassThru & $ADModule { Get-WinADForestOptionalFeatures -WarningAction SilentlyContinue } } function Test-ADRolesAvailability { param([string] $Domain) $ADModule = Import-Module PSWinDocumentation.AD -PassThru $Roles = & $ADModule { param($Domain); Get-WinADForestRoles -Domain $Domain } $Domain if ($Domain -ne '') { [PSCustomObject] @{PDCEmulator = $Roles['PDCEmulator'] PDCEmulatorAvailability = if ($Roles['PDCEmulator']) { (Test-NetConnection -ComputerName $Roles['PDCEmulator']).PingSucceeded } else { $false } RIDMaster = $Roles['RIDMaster'] RIDMasterAvailability = if ($Roles['RIDMaster']) { (Test-NetConnection -ComputerName $Roles['RIDMaster']).PingSucceeded } else { $false } InfrastructureMaster = $Roles['InfrastructureMaster'] InfrastructureMasterAvailability = if ($Roles['InfrastructureMaster']) { (Test-NetConnection -ComputerName $Roles['InfrastructureMaster']).PingSucceeded } else { $false } } } else { [PSCustomObject] @{SchemaMaster = $Roles['SchemaMaster'] SchemaMasterAvailability = if ($Roles['SchemaMaster']) { (Test-NetConnection -ComputerName $Roles['SchemaMaster']).PingSucceeded } else { $false } DomainNamingMaster = $Roles['DomainNamingMaster'] DomainNamingMasterAvailability = if ($Roles['DomainNamingMaster']) { (Test-NetConnection -ComputerName $Roles['DomainNamingMaster']).PingSucceeded } else { $false } } } } function Test-FSMORolesAvailability { param([string] $Domain = $Env:USERDNSDOMAIN) $DC = Get-ADDomainController -Server $Domain -Filter * $Output = foreach ($S in $DC) { if ($S.OperationMasterRoles.Count -gt 0) { $Status = Test-Connection -ComputerName $S.HostName -Count 2 -Quiet } else { $Status = $null } foreach ($_ in $S.OperationMasterRoles) { [PSCustomObject] @{Role = $_ HostName = $S.HostName Status = $Status } } } $Output } $Script:SBForestRoles = { Test-ADRolesAvailability } $Script:SBDomainRoles = { Test-ADRolesAvailability -Domain $Domain } $Script:SBTestFSMODomainRoles = { Test-FSMORolesAvailability -Domain $Domain } function Test-ADSites { param() $ADModule = Import-Module PSWinDocumentation.AD -PassThru $Sites = & $ADModule { Get-WinADForestSites } [Array] $SitesWithoutDC = $Sites | Where-Object { $_.DomainControllersCount -eq 0 } [Array] $SitesWithoutSubnets = $Sites | Where-Object { $_.SubnetsCount -eq 0 } [PSCustomObject] @{SitesWithoutDC = $SitesWithoutDC.Count SitesWithoutSubnets = $SitesWithoutSubnets.Count SitesWithoutDCName = $SitesWithoutDC.Name -join ', ' SitesWithoutSubnetsName = $SitesWithoutSubnets.Name -join ', ' } } $Script:SBForestSites = { Test-ADSites } $Script:SBForestSiteLinks = { Get-WinADSiteLinks } function Test-ADSiteLinks { param([string] $Splitter) [Array] $SiteLinks = Get-WinADSiteConnections $Collection = @($SiteLinks).Where( { $_.Options -notcontains 'IsGenerated' -and $_.EnabledConnection -eq $true }, 'Split') $LinksManual = foreach ($Link in $Collection[0]) { "$($Link.ServerFrom) to $($Link.ServerTo)" } $LinksAutomatic = foreach ($Link in $Collection[1]) { "$($Link.ServerFrom) to $($Link.ServerTo)" } $CollectionNotifications = @($SiteLinks).Where( { $_.Options -notcontains 'UseNotify' -and $_.EnabledConnection -eq $true }, 'Split') $LinksNotUsingNotifications = foreach ($Link in $CollectionNotifications[0]) { "$($Link.ServerFrom) to $($Link.ServerTo)" } $LinksUsingNotifications = foreach ($Link in $CollectionNotifications[1]) { "$($Link.ServerFrom) to $($Link.ServerTo)" } [ordered] @{SiteLinksManual = if ($Splitter -eq '') { $LinksManual } else { $LinksManual -join $Splitter } SiteLinksAutomatic = if ($Splitter -eq '') { $LinksAutomatic } else { $LinksAutomatic -join $Splitter } SiteLinksUseNotify = if ($Splitter -eq '') { $LinksUsingNotifications } else { $LinksUsingNotifications -join $Splitter } SiteLinksNotUsingNotify = if ($Splitter -eq '') { $LinksNotUsingNotifications } else { $LinksNotUsingNotifications -join $Splitter } SiteLinksUseNotifyCount = $CollectionNotifications[1].Count SiteLinksNotUsingNotifyCount = $CollectionNotifications[0].Count SiteLinksManualCount = $Collection[0].Count SiteLinksAutomaticCount = $Collection[1].Count SiteLinksTotalCount = ($SiteLinks | Where-Object { $_.EnabledConnection -eq $true }).Count } } $Script:SBForestSiteLinksConnections = { Test-ADSiteLinks -Splitter ', ' } $Script:SBResolveDNSExternal = { $Output = Invoke-Command -ComputerName $DomainController -ErrorAction Stop { Resolve-DnsName -Name 'evotec.xyz' -ErrorAction SilentlyContinue } $Output } $Script:SBResolveDNSInternal = { $Output = Invoke-Command -ComputerName $DomainController -ErrorAction Stop { param([string] $DomainController) $AllDomainControllers = Get-ADDomainController -Identity $DomainController -Server $DomainController $IPs = $AllDomainControllers.IPv4Address | Sort-Object $Output = Resolve-DnsName -Name $DomainController -ErrorAction SilentlyContinue @{'Result' = 'IP Comparison' 'Status' = if ($null -eq (Compare-Object -ReferenceObject $IPs -DifferenceObject ($Output.IP4Address | Sort-Object))) { $true } else { $false } 'IPAddresses' = $Output.IP4Address } } -ArgumentList $DomainController $Output } $Script:SBDomainControllersPort53 = { Test-NetConnection -ComputerName $DomainController -WarningAction SilentlyContinue -Port 53 } $Script:SBTestServerPorts = { $TcpPorts = @(53, 88, 135, 139, 389, 445, 464, 636, 3268, 3269, 9389) Test-ComputerPort -ComputerName $DomainController -PortTCP $TcpPorts -WarningAction SilentlyContinue } $Script:SBTestServerPortsRDP = { $TcpPorts = @(3389) Test-ComputerPort -ComputerName $DomainController -PortTCP $TcpPorts -WarningAction SilentlyContinue } $Script:SBWindowsRemoteManagement = { Test-WinRM -ComputerName $DomainController } $Script:SBTestWindowsUpdates = { Get-HotFix -ComputerName $DomainController | Sort-Object -Property InstalledOn -Descending | Select-Object -First 1 } $Script:SBKeberosAccountTimeChange = { Get-ADUser krbtgt -Properties Created, PasswordLastSet, msDS-KeyVersionNumber -Server $Domain } $Script:SBGroupsAccountOperators = { Get-ADGroupMember -Identity 'S-1-5-32-548' -Recursive -Server $Domain } $Script:SBUsersAccountAdministrator = { $DomainSID = (Get-ADDomain -Server $Domain).DomainSID $User = Get-ADUser -Identity "$DomainSID-500" -Properties PasswordLastSet, LastLogonDate, servicePrincipalName -Server $Domain if ($User.Enabled -eq $false) { [PSCustomObject] @{Name = 'Administrator' PasswordLastSet = Get-Date } } else { [PSCustomObject] @{Name = 'Administrator' PasswordLastSet = $User.PasswordLastSet } } } $Script:SBGroupSchemaAdmins = { $DomainSID = (Get-ADDomain -Server $Domain).DomainSID Get-ADGroupMember -Recursive -Server $Domain -Identity "$DomainSID-518" } function Test-DNSNameServers { [cmdletBinding()] param([string] $DomainController, [string] $Domain) if ($DomainController) { $AllDomainControllers = (Get-ADDomainController -Server $Domain -Filter { IsReadOnly -eq $false }).HostName try { $Hosts = Get-DnsServerResourceRecord -ZoneName $Domain -ComputerName $DomainController -RRType NS -ErrorAction Stop $NameServers = (($Hosts | Where-Object { $_.HostName -eq '@' }).RecordData.NameServer) -replace ".$" $Compare = ((Compare-Object -ReferenceObject $AllDomainControllers -DifferenceObject $NameServers -IncludeEqual).SideIndicator -notin @('=>', '<=')) [PSCustomObject] @{DomainControllers = $AllDomainControllers NameServers = $NameServers Status = $Compare Comment = "Name servers found $($NameServers -join ', ')" } } catch { [PSCustomObject] @{DomainControllers = $AllDomainControllers NameServers = $null Status = $false Comment = $_.Exception.Message } } } } $Script:SBServerDnsNameServers = { Test-DNSNameServers @args } $Script:SBDomainTrustsData = { $ADModule = Import-Module PSWinDocumentation.AD -PassThru & $ADModule { param($Domain) Get-WinADDomainTrusts -Domain $Domain } $Domain @args } $Script:SBDomainTrustsConnectivity = { foreach ($_ in $Object) { Test-Value -TestName "Trust status verification | Source $Domain, Target $($_.'Trust Target'), Direction $($_.'Trust Direction')" -Property 'Trust Status' -ExpectedValue 'OK' -Object $_ -Level $LevelTest -Domain $Domain -DomainController $DomainController } } $Script:SBDomainTrustsUnconstrainedDelegation = { foreach ($_ in $Object) { if ($($_.'Trust Direction' -eq 'BiDirectional' -or $_.'Trust Direction' -eq 'InBound')) { Test-Value -TestName "Trust Unconstrained TGTDelegation | Source $Domain, Target $($_.'Trust Target'), Direction $($_.'Trust Direction')" -Property 'TGTDelegation' -ExpectedValue $True -Object $_ -Level $LevelTest -Domain $Domain -DomainController $DomainController } } } $Script:SBForestLastBackup = { Get-WinADLastBackup } $Script:SBForestLastBackupTest = { foreach ($_ in $Object) { Test-Value -Level $LevelTest -TestName "Last Backup $($_.NamingContext)" -Object $_ -Property 'LastBackupDaysAgo' -PropertyExtendedValue 'LastBackup' -OperationType 'lt' -ExpectedValue 2 -Domain $Domain -DomainController $DomainController } } $Script:SBForestReplication = { Get-WinADForestReplication -WarningAction SilentlyContinue } $Script:SBForestReplicationTest1 = { foreach ($_ in $Object) { Test-Value -TestName "Replication from $($_.Server) to $($_.ServerPartner)" -Property 'Status' -PropertyExtendedValue 'StatusMessage' -ExpectedValue $True -Level $LevelTest -Object $_ -Domain $Domain -DomainController $DomainController } } function Get-TestimoDomain { [CmdletBinding()] param([string] $Domain) $Output = Get-ADDomain -Server $Domain -ErrorAction Stop $Output } function Get-TestimoDomainControllers { [CmdletBinding()] param([string] $Domain) try { $DomainControllers = Get-ADDomainController -Server $Domain -Filter * -ErrorAction Stop foreach ($_ in $DomainControllers) { if ($_.HostName -notin $Script:TestimoConfiguration['Exclusions']['DomainControllers']) { [PSCustomObject] @{Name = $($_.HostName).ToLower() IsPDC = $_.OperationMasterRoles -contains 'PDCEmulator' } } } } catch { return } } function Get-TestimoForest { [CmdletBinding()] param() try { $Forest = Get-ADForest -ErrorAction Stop $Domains = foreach ($_ in $Forest.Domains) { if ($_ -notin $Script:TestimoConfiguration['Exclusions']['Domains']) { $_.ToLower() } } [ordered] @{Name = $Forest.Name ForestMode = $Forest.ForestMode Domains = $Domains PartitionsContainer = $Forest.PartitionsContainer DomainNamingMaster = $Forest.DomainNamingMaster SchemaMaster = $Forest.SchemaMaster GlobalCatalogs = $Forest.GlobalCatalogs Sites = $Forest.Sites SPNSuffixes = $Forest.SPNSuffixes UPNSuffixes = $Forest.UPNSuffixes ApplicationPartitions = $Forest.ApplicationPartitions CrossForestReferences = $Forest.CrossForestReferences } } catch { return } } function Out-Begin { [CmdletBinding()] param([string] $Text, [int] $Level, [string] $Type = 't', [string] $Domain, [string] $DomainController) if ($Domain -and $DomainController) { if ($Type -eq 't') { [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow } else { [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow } $TestText = "[$Type]", "[$Domain]", "[$($DomainController)] ", $Text } elseif ($Domain) { if ($Type -eq 't') { [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow } else { [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow } $TestText = "[$Type]", "[$Domain] ", $Text } elseif ($DomainController) { Write-Warning "Out-Begin - Shouldn't happen - Fix me." } else { if ($Type -eq 't') { [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow } else { [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow } $TestText = "[$Type]", "[Forest] ", $Text } Write-Color -Text $TestText -Color $Color -StartSpaces $Level -NoNewLine } function Out-Failure { param([string] $Text, [int] $Level, [string] $ExtendedValue = 'Input data not provided. Failing test.', [string] $Domain, [string] $DomainController) Out-Begin -Text $Text -Level $Level -Domain $Domain -DomainController $DomainController Out-Status -Text $Text -Status $false -ExtendedValue $ExtendedValue -Domain $Domain -DomainController $DomainController } function Out-Status { [CmdletBinding()] param([string] $Text, [nullable[bool]] $Status, [string] $Section, [string] $ExtendedValue, [string] $Domain, [string] $DomainController) if ($Status -eq $true) { [string] $TextStatus = 'Pass' [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::Green, [ConsoleColor]::Cyan, [ConsoleColor]::Cyan, [ConsoleColor]::Green, [ConsoleColor]::Cyan } elseif ($Status -eq $false) { [string] $TextStatus = 'Fail' [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::Red, [ConsoleColor]::Cyan, [ConsoleColor]::Cyan, [ConsoleColor]::Red, [ConsoleColor]::Cyan } else { [string] $TextStatus = 'Informative' [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Cyan, [ConsoleColor]::Cyan, [ConsoleColor]::Magenta, [ConsoleColor]::Cyan } if ($ExtendedValue) { Write-Color -Text ' [', $TextStatus, ']', " [", $ExtendedValue, "]" -Color $Color } else { Write-Color -Text ' [', $TextStatus, ']' -Color $Color } if ($Domain -and $DomainController) { $TestType = 'Domain Controller' $TestText = "Domain Controller - $DomainController | $Text" } elseif ($Domain) { $TestType = 'Domain' $TestText = "Domain - $Domain | $Text" } elseif ($DomainController) { $TestType = 'Should not happen. Find an error.' } else { $TestType = 'Forest' $TestText = "Forest | $Text" } if ($null -ne $Status) { $Script:TestResults.Add([PSCustomObject]@{Name = $TestText Type = $TestType Domain = $Domain DomainController = $DomainController Status = $Status Extended = $ExtendedValue }) } } function Out-Summary { [CmdletBinding()] param([System.Diagnostics.Stopwatch] $Time, $Text, [int] $Level, [string] $Domain, [string] $DomainController, [PSCustomobject] $TestsSummary) $EndTime = Stop-TimeLog -Time $Time -Option OneLiner $Type = 'i' if ($Domain -and $DomainController) { if ($Type -eq 't') { [ConsoleColor[]] $Color = @([ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray) } else { [ConsoleColor[]] $Color = @([ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray [ConsoleColor]::Yellow, [ConsoleColor]::White, [ConsoleColor]::Yellow [ConsoleColor]::Green [ConsoleColor]::Yellow [ConsoleColor]::Red [ConsoleColor]::Yellow [ConsoleColor]::Cyan) } $TestText = @("[$Type]", "[$Domain]", "[$($DomainController)] ", $Text, ' [', 'Time to execute tests: ', $EndTime, ']', '[', 'Tests Total: ', ($TestsSummary.Total), ', Passed: ', ($TestsSummary.Passed), ', Failed: ', ($TestsSummary.Failed), ', Skipped: ', ($TestsSummary.Skipped), ']') } elseif ($Domain) { if ($Type -eq 't') { [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray } else { [ConsoleColor[]] $Color = @([ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray [ConsoleColor]::Yellow, [ConsoleColor]::White, [ConsoleColor]::Yellow [ConsoleColor]::Green [ConsoleColor]::Yellow [ConsoleColor]::Red [ConsoleColor]::Yellow [ConsoleColor]::Cyan) } $TestText = @("[$Type]", "[$Domain] ", $Text, ' [', 'Time to execute tests: ', $EndTime, ']', '[', 'Tests Total: ', ($TestsSummary.Total), ', Passed: ', ($TestsSummary.Passed), ', Failed: ', ($TestsSummary.Failed), ', Skipped: ', ($TestsSummary.Skipped), ']') } elseif ($DomainController) { Write-Warning "Out-Begin - Shouldn't happen - Fix me." } else { if ($Type -eq 't') { [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray } else { [ConsoleColor[]] $Color = @([ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray [ConsoleColor]::Yellow, [ConsoleColor]::White, [ConsoleColor]::Yellow [ConsoleColor]::Green [ConsoleColor]::Yellow [ConsoleColor]::Red [ConsoleColor]::Yellow [ConsoleColor]::Cyan) } $TestText = @("[$Type]", "[Forest] ", $Text, ' [', 'Time to execute tests: ', $EndTime, ']', '[', 'Tests Total: ', ($TestsSummary.Total), ', Passed: ', ($TestsSummary.Passed), ', Failed: ', ($TestsSummary.Failed), ', Skipped: ', ($TestsSummary.Skipped), ']') } Write-Color -Text $TestText -Color $Color -StartSpaces $Level } function Start-Testing { [CmdletBinding()] param([ScriptBlock] $Execute, [string] $Scope, [string] $Domain, [string] $DomainController, [bool] $IsPDC, [Object] $ForestInformation, [Object] $DomainInformation) $GlobalTime = Start-TimeLog if ($Scope -eq 'Forest') { $Level = 3 $LevelTest = 6 $LevelSummary = 3 $LevelTestFailure = 6 } elseif ($Scope -eq 'Domain') { $Level = 6 $LevelTest = 9 $LevelSummary = 6 $LevelTestFailure = 9 } elseif ($Scope -eq 'DomainControllers') { $Level = 9 $LevelTest = 12 $LevelSummary = 9 $LevelTestFailure = 12 } else { } if ($Domain -and $DomainController) { $SummaryText = "Domain $Domain, $DomainController" } elseif ($Domain) { Write-Color $SummaryText = "Domain $Domain" } else { $SummaryText = "Forest" } [bool] $IsDomainRoot = $ForestInformation.Name -eq $Domain Out-Begin -Type 'i' -Text $SummaryText -Level ($LevelSummary - 3) -Domain $Domain -DomainController $DomainController Out-Status -Text $SummaryText -Status $null -ExtendedValue '' -Domain $Domain -DomainController $DomainController $TestsSummaryTogether = @(foreach ($Source in $($Script:TestimoConfiguration.$Scope.Sources.Keys)) { $CurrentSection = $Script:TestimoConfiguration.$Scope.Sources[$Source] if ($CurrentSection['Enable'] -eq $true) { $CurrentSource = $CurrentSection['Source'] [Array] $AllTests = $CurrentSection['Tests'].Keys $TestsSummary = [PSCustomobject] @{Passed = 0 Failed = 0 Skipped = 0 Total = 0 } $Time = Start-TimeLog if ($CurrentSource['Requirements']) { if ($null -ne $CurrentSource['Requirements']['IsDomainRoot']) { if (-not $CurrentSource['Requirements']['IsDomainRoot'] -eq $IsDomainRoot) { continue } } if ($null -ne $CurrentSource['Requirements']['IsPDC']) { if (-not $CurrentSource['Requirements']['IsPDC'] -eq $IsPDC) { continue } } } if ($CurrentSource['Parameters']) { $SourceParameters = $CurrentSource['Parameters'] $Object = Start-TestProcessing -Test $CurrentSource['Name'] -Level $Level -OutputRequired -Domain $Domain -DomainController $DomainController { & $CurrentSource['Data'] @SourceParameters -DomainController $DomainController -Domain $Domain } } else { $Object = Start-TestProcessing -Test $CurrentSource['Name'] -Level $Level -OutputRequired -Domain $Domain -DomainController $DomainController { & $CurrentSource['Data'] -DomainController $DomainController -Domain $Domain } } if ($Object -and ($null -eq $CurrentSource['ExpectedOutput'] -or $CurrentSource['ExpectedOutput'] -eq $true)) { $FailAllTests = $false Out-Begin -Text $CurrentSource['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController Out-Status -Text $CurrentSource['Name'] -Status $true -ExtendedValue 'Data is available.' -Domain $Domain -DomainController $DomainController $TestsSummary.Passed = $TestsSummary.Passed + 1 } elseif ($Object -and $CurrentSource['ExpectedOutput'] -eq $false) { $FailAllTests = $true Out-Failure -Text $CurrentSource['Name'] -Level $LevelTest -ExtendedValue 'Data is available. This is a bad thing.' -Domain $Domain -DomainController $DomainController $TestsSummary.Failed = $TestsSummary.Failed + 1 } elseif ($null -eq $Object -and $CurrentSource['ExpectedOutput'] -eq $false) { $FailAllTests = $false Out-Begin -Text $CurrentSource['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController Out-Status -Text $CurrentSource['Name'] -Status $true -ExtendedValue 'No data returned, which is a good thing.' -Domain $Domain -DomainController $DomainController $TestsSummary.Passed = $TestsSummary.Passed + 1 } else { $FailAllTests = $true Out-Failure -Text $CurrentSource['Name'] -Level $LevelTest -ExtendedValue 'No data available.' -Domain $Domain -DomainController $DomainController $TestsSummary.Failed = $TestsSummary.Failed + 1 } foreach ($Test in $AllTests) { $CurrentTest = $CurrentSection['Tests'][$Test] if ($CurrentTest['Enable'] -eq $True) { if ($CurrentTest['Requirements']) { if ($null -ne $CurrentTest['Requirements']['IsDomainRoot']) { if (-not $CurrentTest['Requirements']['IsDomainRoot'] -eq $IsDomainRoot) { $TestsSummary.Skipped = $TestsSummary.Skipped + 1 continue } } if ($null -ne $CurrentTest['Requirements']['IsPDC']) { if (-not $CurrentTest['Requirements']['IsPDC'] -eq $IsPDC) { $TestsSummary.Skipped = $TestsSummary.Skipped + 1 continue } } } if (-not $FailAllTests) { if ($CurrentTest['Parameters']) { $Parameters = $CurrentTest['Parameters'] } else { $Parameters = $null } $TestsResults = Start-TestingTest -Test $CurrentTest['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController { if ($CurrentTest['Data'] -is [ScriptBlock]) { & $CurrentTest['Data'] -Object $Object -Domain $Domain -DomainController $DomainController @Parameters -Level $LevelTest } else { Test-Value -Object $Object -Domain $Domain -DomainController $DomainController @Parameters -Level $LevelTest -TestName $CurrentTest['Name'] } } $TestsSummary.Passed = $TestsSummary.Passed + ($TestsResults | Where-Object { $_ -eq $true }).Count $TestsSummary.Failed = $TestsSummary.Failed + ($TestsResults | Where-Object { $_ -eq $false }).Count } else { $TestsSummary.Failed = $TestsSummary.Failed + 1 Out-Failure -Text $CurrentTest['Name'] -Level $LevelTestFailure -Domain $Domain -DomainController $DomainController } } else { $TestsSummary.Skipped = $TestsSummary.Skipped + 1 } } $TestsSummary.Total = $TestsSummary.Failed + $TestsSummary.Passed + $TestsSummary.Skipped $TestsSummary Out-Summary -Text $CurrentSource['Name'] -Time $Time -Level $LevelSummary -Domain $Domain -DomainController $DomainController -TestsSummary $TestsSummary } } if ($Execute) { & $Execute }) $TestsSummaryFinal = [PSCustomObject] @{Passed = ($TestsSummaryTogether.Passed | Measure-Object -Sum).Sum Failed = ($TestsSummaryTogether.Failed | Measure-Object -Sum).Sum Skipped = ($TestsSummaryTogether.Skipped | Measure-Object -Sum).Sum Total = ($TestsSummaryTogether.Total | Measure-Object -Sum).Sum } $TestsSummaryFinal Out-Summary -Text $SummaryText -Time $GlobalTime -Level ($LevelSummary - 3) -Domain $Domain -DomainController $DomainController -TestsSummary $TestsSummaryFinal } function Start-TestingTest { [CmdletBinding()] param([ScriptBlock] $Execute, $Test, [int] $Level, [string] $Domain, [string] $DomainController) if ($Execute) { if ($Script:TestimoConfiguration.Debug.ShowErrors) { [Array] $Output = & $Execute $Output } else { try { [Array] $Output = & $Execute $Output } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " } if (-not $ErrorMessage) { } else { Out-Failure -Text $CurrentTest['TestName'] -Level $Level -ExtendedValue $ErrorMessage -Domain $Domain -DomainController $DomainController } } } } function Start-TestProcessing { [CmdletBinding()] param([ScriptBlock] $Execute, [string] $Test, [switch] $OutputRequired, [nullable[bool]] $ExpectedStatus, [int] $Level = 0, [switch] $IsTest, [switch] $Simple, [string] $Domain, [string] $DomainController) if ($Execute) { if ($IsTest) { Out-Begin -Type 't' -Text $Test -Level $Level -Domain $Domain -DomainController $DomainController } else { Out-Begin -Type 'i' -Text $Test -Level $Level -Domain $Domain -DomainController $DomainController } if ($Script:TestimoConfiguration.Debug.ShowErrors) { [Array] $Output = & $Execute $ErrorMessage = $null } else { try { [Array] $Output = & $Execute } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " } } if (-not $ErrorMessage) { foreach ($O in $Output) { if ($OutputRequired.IsPresent) { if ($O['Output']) { foreach ($_ in $O['Output']) { $_ } } else { foreach ($_ in $O) { $_ } } } } if ($null -eq $ExpectedStatus) { $TestResult = $null } else { $TestResult = $ExpectedStatus -eq $Output.Status } Out-Status -Text $Test -Status $TestResult -ExtendedValue $O.Extended -Domain $Domain -DomainController $DomainController } else { Out-Status -Text $Test -Status $false -ExtendedValue $ErrorMessage -Domain $Domain -DomainController $DomainController } } } function Test-Me { param([string] $OperationType, [string] $TestName, [int] $Level, [string] $Domain, [string] $DomainController, [string[]] $Property, [Object] $TestedValue, [Object] $Object, [Array] $ExpectedValue, [string[]] $PropertyExtendedValue, [string] $OperationResult, [int] $ExpectedCount = -1) Out-Begin -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController $TestedValue = $Object foreach ($V in $Property) { $TestedValue = $TestedValue.$V } $ScriptBlock = { $Operators = @{'lt' = 'LessThan' 'gt' = 'GreaterThan' 'le' = 'LessOrEqual' 'ge' = 'GreaterOrEqual' 'eq' = 'Equal' 'contains' = 'Contains' 'like' = 'Like' } if ($ExpectedCount -ne -1) { if ($OperationType -eq 'lt') { $TestResult = $Object.Count -lt $ExpectedCount } elseif ($OperationType -eq 'gt') { $TestResult = $Object.Count -lt $ExpectedCount } elseif ($OperationType -eq 'ge') { $TestResult = $Object.Count -lt $ExpectedCount } elseif ($OperationType -eq 'le') { $TestResult = $Object.Count -lt $ExpectedCount } elseif ($OperationType -eq 'like') { $TestResult = $Object.Count -like $ExpectedCount } elseif ($OperationType -eq 'contains') { $TestResult = $Object.Count -like $ExpectedCount } elseif ($OperationType -eq 'in') { $TestResult = $ExpectedCount -in $Object.Count } elseif ($OperationType -eq 'notin') { $TestResult = $ExpectedCount -notin $Object.Count } else { $TestResult = $Object.Count -lt $ExpectedCount } $TextTestedValue = $Object.Count $ExpectedValue = $ExpectedCount } elseif ($null -ne $ExpectedValue) { if ($null -eq $TestedValue -and $null -ne $ExpectedValue) { $TestResult = $false $TextTestedValue = 'Null' } else { [Array] $TestResult = for ($i = 0; $i -lt $ExpectedValue.Count; $i++) { if ($OperationType -eq 'lt') { $TestedValue -lt $ExpectedValue[$i] } elseif ($OperationType -eq 'gt') { $TestedValue -gt $ExpectedValue[$i] } elseif ($OperationType -eq 'ge') { $TestedValue -ge $ExpectedValue[$i] } elseif ($OperationType -eq 'le') { $TestedValue -le $ExpectedValue[$i] } elseif ($OperationType -eq 'like') { $TestedValue -like $ExpectedValue[$i] } elseif ($OperationType -eq 'contains') { $TestedValue -contains $ExpectedValue[$i] } elseif ($OperationType -eq 'in') { $ExpectedValue -in $ExpectedValue[$i] } elseif ($OperationType -eq 'notin') { $ExpectedValue -notin $ExpectedValue[$i] } else { $TestedValue -eq $ExpectedValue[$i] } } $TextTestedValue = $TestedValue } } else { $TestResult = $null $ExtendedTextValue = "Test provided but no tests required." } if ($null -eq $TestResult) { $ReportResult = $null $ReportExtended = $ExtendedTextValue } else { if ($OperationResult -eq 'OR') { if ($TestResult -contains $true) { $ReportResult = $true $ReportExtended = "Expected value ($($Operators[$OperationType])): $($ExpectedValue)" } else { $ReportResult = $false $ReportExtended = "Expected value ($($Operators[$OperationType])): $ExpectedValue, Found value: $($TextTestedValue)" } } else { if ($TestResult -notcontains $false) { $ReportResult = $true $ReportExtended = "Expected value ($($Operators[$OperationType])): $($ExpectedValue)" } else { $ReportResult = $false $ReportExtended = "Expected value ($($Operators[$OperationType])): $ExpectedValue, Found value: $($TextTestedValue)" } } } if ($PropertyExtendedValue.Count -gt 0) { $ReportExtended = $Object foreach ($V in $PropertyExtendedValue) { $ReportExtended = $ReportExtended.$V } } Out-Status -Text $TestName -Status $ReportResult -ExtendedValue $ReportExtended -Domain $Domain -DomainController $DomainController return $ReportResult } if ($Script:TestimoConfiguration.Debug.ShowErrors) { & $ScriptBlock } else { try { & $ScriptBlock } catch { Out-Status -Text $TestName -Status $false -ExtendedValue $_.Exception.Message -Domain $Domain -DomainController $DomainController return $False } } } function Test-Value { [CmdletBinding()] param([Object] $Object, [string] $TestName, [string[]] $Property, [Object] $ExpectedValue, [string[]] $PropertyExtendedValue, [string] $OperationType, [int] $Level, [string] $Domain, [Object] $DomainController, [int] $ExpectedCount, [string] $OperationResult, [scriptblock] $WhereObject) if ($Object) { if ($ExpectedCount) { Test-Me -Object $Object -ExpectedCount $ExpectedCount -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue -OperationResult $OperationResult } else { if ($WhereObject) { $Object = $Object | Where-Object $WhereObject Test-Me -Object $Object -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue -OperationResult $OperationResult } else { foreach ($_ in $Object) { Test-Me -Object $_ -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue -OperationResult $OperationResult } } } } else { Write-Warning 'Objected not passed to Test-VALUE.' } } $Script:TestimoConfiguration = [ordered] @{Exclusions = @{ } Forest = @{Sources = [ordered] @{OptionalFeatures = @{Enable = $true Source = @{Name = 'Optional Features' Data = $Script:SBForestOptionalFeatures Parameters = @{ } } Tests = [ordered] @{RecycleBinEnabled = @{Enable = $true Name = 'Recycle Bin Enabled' Parameters = @{Property = 'Recycle Bin Enabled' ExpectedValue = $true OperationType = 'eq' } } LapsAvailable = @{Enable = $true Name = 'LAPS Schema Extended' Parameters = @{Property = 'Laps Enabled' ExpectedValue = $true OperationType = 'eq' } } PrivAccessManagement = @{Enable = $true Name = 'Privileged Access Management Enabled' Parameters = @{Property = 'Privileged Access Management Feature Enabled' ExpectedValue = $true OperationType = 'eq' } } } } Replication = @{Enable = $true Source = @{Name = 'Forest Replication' Data = $Script:SBForestReplication Parameters = @{ } } Tests = [ordered] @{ReplicationTests = @{Enable = $true Name = 'Replication Test' Data = $Script:SBForestReplicationTest1 Parameters = @{OperationType = 'eq' } } } } LastBackup = @{Enable = $true Source = @{Name = 'Forest Backup' Data = $Script:SBForestLastBackup Parameters = @{ } } Tests = [ordered] @{LastBackupTests = @{Enable = $true Name = 'Forest Last Backup Time - Context' Data = $Script:SBForestLastBackupTest Parameters = @{ } } } } Sites = @{Enable = $true Source = @{Name = 'Sites Verification' Area = 'Sites' Data = $Script:SBForestSites Parameters = @{ } } Tests = [ordered] @{SitesWithoutDC = @{Enable = $true Name = 'Sites without Domain Controllers' Description = 'Verify each `site has at least [one subnet configured]`' Parameters = @{Property = 'SitesWithoutDC' ExpectedValue = 0 OperationType = 'eq' } } SitesWithoutSubnets = @{Enable = $true Name = 'Sites without Subnets' Parameters = @{Property = 'SitesWithoutSubnets' ExpectedValue = 0 OperationType = 'eq' } } } } SiteLinks = @{Enable = $true Source = @{Name = 'Site Links' Data = $Script:SBForestSiteLinks Area = 'Sites' Parameters = @{ } } Tests = [ordered] @{MinimalReplicationFrequency = @{Enable = $true Name = 'Replication Frequency should be set to maximum 60 minutes' Description = '' Parameters = @{Property = 'ReplicationFrequencyInMinutes' ExpectedValue = 60 OperationType = 'lt' } } UseNotificationsForLinks = @{Enable = $true Name = 'Automatic site links should use notifications' Description = '' Parameters = @{Property = 'Options' ExpectedValue = 'UseNotify' OperationType = 'contains' PropertyExtendedValue = 'Options' } } } } SiteLinksConnections = @{Enable = $true Source = @{Name = 'Site Links Connections' Data = $Script:SBForestSiteLinksConnections Area = 'Sites' Parameters = @{ } } Tests = [ordered] @{AutomaticSiteLinks = @{Enable = $true Name = 'All site links are automatic' Description = 'Verify there are no manually configured sitelinks' Parameters = @{Property = 'SiteLinksManualCount' ExpectedValue = 0 OperationType = 'eq' PropertyExtendedValue = 'SiteLinksManual' } } SiteLinksNotifications = @{Enable = $true Name = 'All site links use notifications' Parameters = @{Property = 'SiteLinksNotUsingNotifyCount' ExpectedValue = 0 OperationType = 'eq' } } SiteLinksDoNotUseNotifications = @{Enable = $false Name = 'All site links are not using notifications' Parameters = @{Property = 'SiteLinksUseNotifyCount' ExpectedValue = 0 OperationType = 'eq' } } } } Roles = @{Enable = $true Source = @{Name = 'Roles availability' Data = $Script:SBForestRoles Area = '' Parameters = @{ } } Tests = [ordered] @{SchemaMasterAvailability = @{Enable = $true Name = 'Schema Master Availability' Parameters = @{ExpectedValue = $true Property = 'SchemaMasterAvailability' OperationType = 'eq' PropertyExtendedValue = 'SchemaMaster' } } DomainNamingMasterAvailability = @{Enable = $true Name = 'Domain Master Availability' Parameters = @{ExpectedValue = $true Property = 'DomainNamingMasterAvailability' OperationType = 'eq' PropertyExtendedValue = 'DomainNamingMaster' } } } } TombstoneLifetime = @{Enable = $true Source = @{Name = 'Tombstone Lifetime' Data = $Script:SBDomainTombstoneLifetime Area = '' Parameters = @{ } } Tests = [ordered] @{TombstoneLifetime = @{Enable = $true Name = 'TombstoneLifetime should be set to minimum of 180 days' Parameters = @{ExpectedValue = 180 Property = 'TombstoneLifeTime' OperationType = 'ge' } } } RecommendedLinks = @('https://helpcenter.netwrix.com/Configure_IT_Infrastructure/AD/AD_Tombstone.html') } } } Domain = @{Sources = [ordered] @{Roles = @{Enable = $true Source = @{Name = 'Roles availability' Data = $Script:SBDomainRoles Area = '' Parameters = @{ } } Tests = [ordered] @{PDCEmulator = @{Enable = $true Name = 'PDC Emulator Availability' Parameters = @{ExpectedValue = $true Property = 'PDCEmulatorAvailability' OperationType = 'eq' PropertyExtendedValue = 'PDCEmulator' } } RIDMaster = @{Enable = $true Name = 'RID Master Availability' Parameters = @{ExpectedValue = $true Property = 'RIDMasterAvailability' OperationType = 'eq' PropertyExtendedValue = 'RIDMaster' } } InfrastructureMaster = @{Enable = $true Name = 'Infrastructure Master Availability' Parameters = @{ExpectedValue = $true Property = 'InfrastructureMasterAvailability' OperationType = 'eq' PropertyExtendedValue = 'InfrastructureMaster' } } } } WellKnownFolders = @{Enable = $true Source = @{Name = 'Well known folders' Data = $Script:SBDomainWellKnownFolders Area = '' Parameters = @{ } } Tests = [ordered] @{UsersContainer = @{Enable = $true Name = "Users Container shouldn't be at default" Parameters = @{WhereObject = { $_.Name -eq 'UsersContainer' } ExpectedValue = $false Property = 'Status' OperationType = 'eq' PropertyExtendedValue = '1' } } ComputersContainer = @{Enable = $true Name = "Computers Container shouldn't be at default" Parameters = @{WhereObject = { $_.Name -eq 'ComputersContainer' } ExpectedValue = $false Property = 'Status' OperationType = 'eq' PropertyExtendedValue = '1' } } DomainControllersContainer = @{Enable = $true Name = "Domain Controllers Container should be at default location" Parameters = @{WhereObject = { $_.Name -eq 'DomainControllersContainer' } ExpectedValue = $true Property = 'Status' OperationType = 'eq' PropertyExtendedValue = '1' } } DeletedObjectsContainer = @{Enable = $true Name = "Deleted Objects Container should be at default location" Parameters = @{WhereObject = { $_.Name -eq 'DeletedObjectsContainer' } ExpectedValue = $true Property = 'Status' OperationType = 'eq' PropertyExtendedValue = '1' } } SystemsContainer = @{Enable = $true Name = "Systems Container should be at default location" Parameters = @{WhereObject = { $_.Name -eq 'SystemsContainer' } ExpectedValue = $true Property = 'Status' OperationType = 'eq' PropertyExtendedValue = '1' } } LostAndFoundContainer = @{Enable = $true Name = "Lost And Found Container should be at default location" Parameters = @{WhereObject = { $_.Name -eq 'LostAndFoundContainer' } ExpectedValue = $true Property = 'Status' OperationType = 'eq' PropertyExtendedValue = '1' } } QuotasContainer = @{Enable = $true Name = "Quotas Container shouldn be at default location" Parameters = @{WhereObject = { $_.Name -eq 'QuotasContainer' } ExpectedValue = $true Property = 'Status' OperationType = 'eq' PropertyExtendedValue = '1' } } ForeignSecurityPrincipalsContainer = @{Enable = $true Name = "Foreign Security Principals Container should be at default location" Parameters = @{WhereObject = { $_.Name -eq 'ForeignSecurityPrincipalsContainer' } ExpectedValue = $true Property = 'Status' OperationType = 'eq' PropertyExtendedValue = '1' } } } } PasswordComplexity = @{Enable = $true Source = @{Name = 'Password Complexity Requirements' Data = $Script:SBDomainPasswordComplexity Area = '' Parameters = @{ } } Tests = [ordered] @{ComplexityEnabled = @{Enable = $true Name = 'Complexity Enabled' Parameters = @{Property = 'Complexity Enabled' ExpectedValue = $true OperationType = 'eq' } } 'Lockout Duration' = @{Enable = $true Name = 'Lockout Duration' Parameters = @{Property = 'Lockout Duration' ExpectedValue = 30 OperationType = 'ge' } } 'Lockout Observation Window' = @{Enable = $true Name = 'Lockout Observation Window' Parameters = @{Property = 'Lockout Observation Window' ExpectedValue = 30 OperationType = 'ge' } } 'Lockout Threshold' = @{Enable = $true Name = 'Lockout Threshold' Parameters = @{Property = 'Lockout Threshold' ExpectedValue = 5 OperationType = 'gt' } } 'Max Password Age' = @{Enable = $true Name = 'Max Password Age' Parameters = @{Property = 'Max Password Age' ExpectedValue = 60 OperationType = 'le' } } 'Min Password Length' = @{Enable = $true Name = 'Min Password Length' Parameters = @{Property = 'Min Password Length' ExpectedValue = 8 OperationType = 'gt' } } 'Min Password Age' = @{Enable = $true Name = 'Min Password Age' Parameters = @{Property = 'Min Password Age' ExpectedValue = 1 OperationType = 'le' } } 'Password History Count' = @{Enable = $true Name = 'Password History Count' Parameters = @{Property = 'Password History Count' ExpectedValue = 10 OperationType = 'ge' } } 'Reversible Encryption Enabled' = @{Enable = $true Name = 'Reversible Encryption Enabled' Parameters = @{Property = 'Reversible Encryption Enabled' ExpectedValue = $false OperationType = 'eq' } } } } Trusts = @{Enable = $true Source = @{Name = "Trust Availability" Data = $Script:SBDomainTrustsData Area = '' Parameters = @{ } } Tests = [ordered] @{TrustsConnectivity = @{Enable = $true Name = 'Trust status verification' Data = $Script:SBDomainTrustsConnectivity } TrustsUnconstrainedDelegation = @{Enable = $true Name = 'Trust Unconstrained TGTDelegation' Data = $Script:SBDomainTrustsUnconstrainedDelegation } } } OrphanedForeignSecurityPrincipals = @{Enable = $true Source = @{Name = "Orphaned Foreign Security Principals" Data = $Script:SBDomainOrphanedFSP Area = 'Cleanup' ExpectedOutput = $false } } EmptyOrganizationalUnits = @{Enable = $true Source = @{Name = "Orphaned/Empty Organizational Units" Data = $Script:SBDomainEmptyOrganizationalUnits Area = 'Cleanup' ExpectedOutput = $false } } DNSScavengingForPrimaryDNSServer = @{Enable = $true Source = @{Name = "DNS Scavenging - Primary DNS Server" Data = $Script:SBDomainDnsScavenging Area = '' Parameters = @{ } } Tests = [ordered] @{ScavengingCount = @{Enable = $true Name = 'Scavenging DNS Servers Count' Parameters = @{ExpectedCount = 1 OperationType = 'eq' } Explanation = 'Scavenging Count should be 1. There should be 1 DNS server per domain responsible for scavenging. If this returns false, every other test fails.' } ScavengingInterval = @{Enable = $true Name = 'Scavenging Interval' Parameters = @{Property = 'ScavengingInterval', 'Days' ExpectedValue = 7 OperationType = 'le' } } 'Scavenging State' = @{Enable = $true Name = 'Scavenging State' Parameters = @{Property = 'ScavengingState' ExpectedValue = $true OperationType = 'eq' } Explanation = 'Scavenging State is responsible for enablement of scavenging for all new zones created.' RecommendedValue = $true ExplanationRecommended = 'It should be enabled so all new zones are subject to scavanging.' DefaultValue = $false } 'Last Scavenge Time' = @{Enable = $true Name = 'Last Scavenge Time' Parameters = @{Property = 'LastScavengeTime' ExpectedValue = (Get-Date).AddDays(-7) OperationType = 'lt' } } } } DNSForwaders = @{Enable = $true Source = @{Name = "DNS Forwarders" Data = $Script:SBDomainDNSForwaders Area = '' Parameters = @{ } } Tests = [ordered] @{SameForwarders = @{Enable = $true Name = 'Same DNS Forwarders' Parameters = @{Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Source' } Explanation = 'DNS forwarders within one domain should have identical setup' } } } DnsZonesAging = @{Enable = $true Source = @{Name = "Aging primary DNS Zone" Data = $Script:SBDomainDnsZones Area = '' Parameters = @{ } } Tests = [ordered] @{EnabledAgingEnabled = @{Enable = $true Name = 'Zone DNS aging should be enabled' Parameters = @{Property = 'Source' ExpectedValue = $true OperationType = 'eq' } Explanation = 'Primary DNS zone should have aging enabled.' } EnabledAgingIdentical = @{Enable = $true Name = 'Zone DNS aging should be identical on all DCs' Parameters = @{Property = 'Status' ExpectedValue = $true OperationType = 'eq' } Explanation = 'Primary DNS zone should have aging enabled, on all DNS servers.' } } } KerberosAccountAge = @{Enable = $true Source = @{Name = "Kerberos Account Age" Data = $Script:SBKeberosAccountTimeChange Area = '' Parameters = @{ } } Tests = [ordered] @{EnabledAgingEnabled = @{Enable = $true Name = 'Kerberos Last Password Change Should be less than 180 days' Parameters = @{Property = 'PasswordLastSet' ExpectedValue = (Get-Date).AddDays(-180) OperationType = 'gt' } Explanation = '' } } } SecurityGroupsAccountOperators = @{Enable = $true Source = @{Name = "Groups: Account operators should be empty" Data = $Script:SBGroupsAccountOperators Area = '' Parameters = @{ } ExpectedOutput = $false Explanation = "The Account Operators group should not be used. Custom delegate instead. This group is a great 'backdoor' priv group for attackers. Microsoft even says don't use this group!" } } SecurityGroupsSchemaAdmins = @{Enable = $true Source = @{Name = "Groups: Schema Admins should be empty" Data = $Script:SBGroupSchemaAdmins Area = '' Parameters = @{ } Requirements = @{IsDomainRoot = $true } ExpectedOutput = $false Explanation = "Schema Admins should be empty." } } SecurityUsersAcccountAdministrator = @{Enable = $true Source = @{Name = "Users: Administrator" Data = $Script:SBUsersAccountAdministrator Area = '' Parameters = @{ } } Tests = [ordered] @{PasswordLastSet = @{Enable = $true Name = 'Administrator Last Password Change Should be less than 360 days ago' Parameters = @{Property = 'PasswordLastSet' ExpectedValue = (Get-Date).AddDays(-360) OperationType = 'gt' } Explanation = 'Administrator account should be disabled or LastPasswordChange should be less than 1 year ago.' } } } } } DomainControllers = @{Sources = [ordered] @{WindowsRemoteManagement = @{Enable = $true Source = @{Name = 'Windows Remote Management' Data = $Script:SBWindowsRemoteManagement Area = '' Parameters = @{ } } Tests = [ordered] @{OperatingSystem = @{Enable = $true Name = 'Test submits an identification request that determines whether the WinRM service is running.' Parameters = @{Property = 'Status' ExpectedValue = $true OperationType = 'eq' } } } } OperatingSystem = @{Enable = $true Source = @{Name = 'Operating System' Data = $Script:SBComputerOperatingSystem Area = '' Parameters = @{ } } Tests = [ordered] @{OperatingSystem = @{Enable = $true Name = 'Operating system Windows Server 2012 and up' Parameters = @{Property = 'OperatingSystem' ExpectedValue = 'Microsoft Windows Server 2019*', 'Microsoft Windows Server 2016*', 'Microsoft Windows Server 2012*' OperationType = 'like' OperationResult = 'OR' PropertyExtendedValue = 'OperatingSystem' } } } } Services = @{Enable = $true Source = @{Name = 'Service Status' Data = $Script:SBDomainControllersServices Area = '' Parameters = @{ } } Tests = [ordered] @{ADWSServiceStatus = @{Enable = $true Name = 'ADWS Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'ADWS' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } ADWSServiceStartType = @{Enable = $true Name = 'ADWS Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'ADWS' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } DNSServiceStatus = @{Enable = $true Name = 'DNS Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'DNS' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } DNSServiceStartType = @{Enable = $true Name = 'DNS Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'DNS' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } DFSServiceStatus = @{Enable = $true Name = 'DFS Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'DFS' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } DFSServiceStartType = @{Enable = $true Name = 'DFS Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'DFS' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } DFSRServiceStatus = @{Enable = $true Name = 'DFSR Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'DFSR' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } DFSRServiceStartType = @{Enable = $true Name = 'DFSR Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'DFSR' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } EventlogServiceStatus = @{Enable = $true Name = 'Eventlog Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'Eventlog' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } EventlogServiceStartType = @{Enable = $true Name = 'Eventlog Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'Eventlog' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } EventSystemServiceStatus = @{Enable = $true Name = 'EventSystem Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'EventSystem' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } EventSystemServiceStartType = @{Enable = $true Name = 'EventSystem Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'EventSystem' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } KDCServiceStatus = @{Enable = $true Name = 'KDC Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'KDC' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } KDCServiceStartType = @{Enable = $true Name = 'KDC Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'KDC' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } LanManWorkstationServiceStatus = @{Enable = $true Name = 'LanManWorkstation Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'LanManWorkstation' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } LanManWorkstationServiceStartType = @{Enable = $true Name = 'LanManWorkstation Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'LanManWorkstation' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } LanManServerServiceStatus = @{Enable = $true Name = 'LanManServer Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'LanManServer' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } LanManServerServiceStartType = @{Enable = $true Name = 'LanManServer Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'LanManServer' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } NetLogonServiceStatus = @{Enable = $true Name = 'NetLogon Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'NetLogon' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } NetLogonServiceStartType = @{Enable = $true Name = 'NetLogon Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'NetLogon' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } NTDSServiceStatus = @{Enable = $true Name = 'NTDS Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'NTDS' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } NTDSServiceStartType = @{Enable = $true Name = 'NTDS Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'NTDS' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } RPCSSServiceStatus = @{Enable = $true Name = 'RPCSS Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'RPCSS' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } RPCSSServiceStartType = @{Enable = $true Name = 'RPCSS Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'RPCSS' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } SAMSSServiceStatus = @{Enable = $true Name = 'SAMSS Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'SAMSS' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } SAMSSServiceStartType = @{Enable = $true Name = 'SAMSS Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'SAMSS' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } SpoolerServiceStatus = @{Enable = $true Name = 'Spooler Service is STOPPED' Parameters = @{WhereObject = { $_.Name -eq 'Spooler' } Property = 'Status' ExpectedValue = 'Stopped' OperationType = 'eq' } } SpoolerServiceStartType = @{Enable = $true Name = 'Spooler Service START TYPE is DISABLED' Parameters = @{WhereObject = { $_.Name -eq 'Spooler' } Property = 'StartType' ExpectedValue = 'Disabled' OperationType = 'eq' } } W32TimeServiceStatus = @{Enable = $true Name = 'W32Time Service is RUNNING' Parameters = @{WhereObject = { $_.Name -eq 'W32Time' } Property = 'Status' ExpectedValue = 'Running' OperationType = 'eq' } } W32TimeServiceStartType = @{Enable = $true Name = 'W32Time Service START TYPE is Automatic' Parameters = @{WhereObject = { $_.Name -eq 'W32Time' } Property = 'StartType' ExpectedValue = 'Automatic' OperationType = 'eq' } } } } LDAP = @{Enable = $true Source = @{Name = 'LDAP Connectivity' Data = $Script:SBDomainControllersLDAP Area = '' Parameters = @{ } } Tests = [ordered] @{PortLDAP = @{Enable = $true Name = 'LDAP Port is Available' Parameters = @{Property = 'LDAP' ExpectedValue = $true OperationType = 'eq' } } PortLDAPS = @{Enable = $true Name = 'LDAP SSL Port is Available' Parameters = @{Property = 'LDAPS' ExpectedValue = $true OperationType = 'eq' } } PortLDAP_GC = @{Enable = $true Name = 'LDAP GC Port is Available' Parameters = @{Property = 'GlobalCatalogLDAP' ExpectedValue = $true OperationType = 'eq' } } PortLDAPS_GC = @{Enable = $true Name = 'LDAP SSL GC Port is Available' Parameters = @{Property = 'GlobalCatalogLDAPS' ExpectedValue = $true OperationType = 'eq' } } } } Pingable = @{Enable = $true Source = @{Name = 'Ping Connectivity' Data = $Script:SBDomainControllersPing Area = '' Parameters = @{ } } Tests = @{Ping = @{Enable = $true Name = 'Responding to PING' Parameters = @{Property = 'PingSucceeded' PropertyExtendedValue = 'PingReplyDetails', 'RoundtripTime' ExpectedValue = $true OperationType = 'eq' } } } } Ports = @{Enable = $true Source = @{Name = 'AD TCP Ports are open' Data = $Script:SBTestServerPorts Area = '' Parameters = @{ } } Tests = @{Ping = @{Enable = $true Name = 'Port is OPEN' Parameters = @{Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } } } PortsRDP = @{Enable = $true Source = @{Name = 'RDP Ports is open' Data = $Script:SBTestServerPortsRDP Area = '' Parameters = @{ } } Tests = @{Ping = @{Enable = $true Name = 'Port is OPEN' Parameters = @{Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } } } DiskSpace = @{Enable = $true Source = @{Name = 'Disk Free' Data = $Script:SBDomainControllersDiskSpace Area = '' Parameters = @{ } } Tests = @{FreeSpace = @{Enable = $true Name = 'Free Space in GB' Parameters = @{Property = 'FreeSpace' PropertyExtendedValue = 'FreeSpace' ExpectedValue = 10 OperationType = 'gt' } } FreePercent = @{Enable = $true Name = 'Free Space Percent' Parameters = @{Property = 'FreePercent' PropertyExtendedValue = 'FreePercent' ExpectedValue = 10 OperationType = 'gt' } } } } TimeSettings = [ordered] @{Enable = $true Source = @{Name = "Time Settings" Data = $Script:SBDomainControllersTimeSettings Area = '' Parameters = @{ } } Tests = [ordered] @{NTPServerEnabled = @{Enable = $true Name = 'NtpServer must be enabled.' Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController } Property = 'NtpServerEnabled' ExpectedValue = $true OperationType = 'eq' } } VMTimeProvider = @{Enable = $true Name = 'Virtual Machine Time Provider should be disabled.' Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController } Property = 'VMTimeProvider' ExpectedValue = $false OperationType = 'eq' } } NtpTypeNonPDC = [ordered] @{Enable = $true Name = 'NTP Server should be set to Domain Hierarchy' Requirements = @{IsPDC = $false } Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController } Property = 'NtpType' ExpectedValue = 'NT5DS' OperationType = 'eq' } } NtpTypePDC = [ordered] @{Enable = $true Name = 'NTP Server should be set to AllSync' Requirements = @{IsPDC = $true } Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController } Property = 'NtpType' ExpectedValue = 'AllSync' OperationType = 'eq' } } } } TimeSynchronizationInternal = @{Enable = $true Source = @{Name = "Time Synchronization Internal" Data = $Script:SBDomainTimeSynchronizationInternal Area = '' Parameters = @{ } } Tests = [ordered] @{TimeSynchronizationTest = @{Enable = $true Name = 'Time Difference' Parameters = @{Property = 'TimeDifferenceSeconds' ExpectedValue = 1 OperationType = 'le' PropertyExtendedValue = 'TimeDifferenceSeconds' } } } MicrosoftMaterials = 'https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc773263(v=ws.10)#w2k3tr_times_tools_uhlp' } TimeSynchronizationExternal = @{Enable = $true Source = @{Name = "Time Synchronization External" Data = $Script:SBDomainTimeSynchronizationExternal Area = '' Parameters = @{ } } Tests = [ordered] @{TimeSynchronizationTest = @{Enable = $true Name = 'Time Difference' Parameters = @{Property = 'TimeDifferenceSeconds' ExpectedValue = 1 OperationType = 'le' PropertyExtendedValue = 'TimeDifferenceSeconds' } } } MicrosoftMaterials = 'https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc773263(v=ws.10)#w2k3tr_times_tools_uhlp' } WindowsFirewall = @{Enable = $true Source = @{Name = "Windows Firewall" Data = $Script:SBDomainControllersFirewall Area = 'Connectivity' Description = 'Verify windows firewall should be enabled for all network cards' Parameters = @{ } } Tests = [ordered] @{TimeSynchronizationTest = @{Enable = $true Name = 'Windows Firewall should be enabled on network card' Parameters = @{Property = 'FirewallStatus' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'FirewallProfile' } } } } WindowsUpdates = @{Enable = $true Source = @{Name = "Windows Updates" Data = $Script:SBTestWindowsUpdates Area = '' Parameters = @{ } } Tests = [ordered] @{PasswordLastSet = @{Enable = $true Name = 'Last Windows Updates should be less than 60 days ago' Parameters = @{Property = 'InstalledOn' ExpectedValue = (Get-Date).AddDays(-60) OperationType = 'gt' } Explanation = 'Last installed update should be less than 60 days ago.' } } } DnsResolveInternal = @{Enable = $true Source = @{Name = "Resolves internal DNS queries" Data = $Script:SBResolveDNSInternal Area = '' Parameters = @{ } } Tests = [ordered] @{ResolveDNSInternal = @{Enable = $true Name = 'Should resolve Internal DNS' Parameters = @{Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'IPAddresses' } Explanation = 'DNS should resolve internal domains correctly.' } } } DnsResolveExternal = @{Enable = $true Source = @{Name = "Resolves external DNS queries" Data = $Script:SBResolveDNSExternal Area = '' Parameters = @{ } } Tests = [ordered] @{ResolveDNSExternal = @{Enable = $true Name = 'Should resolve External DNS' Parameters = @{Property = 'IPAddress' ExpectedValue = '37.59.176.139' OperationType = 'eq' } Explanation = 'DNS should resolve external queries properly.' } } } DnsNameServes = @{Enable = $true Source = @{Name = "Name servers for primary domain zone" Data = $Script:SBServerDnsNameServers Area = '' Parameters = @{ } } Tests = [ordered] @{DnsNameServersIdentical = @{Enable = $true Name = 'DNS Name servers for primary zone are identical' Parameters = @{Property = 'Status' ExpectedValue = $True OperationType = 'eq' PropertyExtendedValue = 'Comment' } Explanation = 'DNS Name servers for primary zone should be equal to Domain Controllers for a Domain.' } } } SMBProtocols = @{Enable = $true Source = @{Name = 'SMB Protocols' Data = $Script:SBDomainControllersSMB Area = '' Parameters = @{ } } Tests = [ordered] @{EnableSMB1Protocol = @{Enable = $true Name = 'SMB v1 Protocol should be disabled' Parameters = @{Property = 'EnableSMB1Protocol' ExpectedValue = $false OperationType = 'eq' } } EnableSMB2Protocol = @{Enable = $true Name = 'SMB v2 Protocol should be enabled' Parameters = @{Property = 'EnableSMB2Protocol' ExpectedValue = $true OperationType = 'eq' } } } } DFSRAutoRecovery = @{Enable = $true Source = @{Name = 'DFSR AutoRecovery' Data = $Script:SBDomainControllersDFSR Area = '' Parameters = @{ } RecommendedLinks = @('https://secureinfra.blog/2019/04/30/field-notes-a-quick-tip-on-dfsr-automatic-recovery-while-you-prepare-for-an-ad-domain-upgrade/') } Tests = [ordered] @{EnableSMB1Protocol = @{Enable = $true Name = 'DFSR AutoRecovery should be enabled' Parameters = @{Property = 'StopReplicationOnAutoRecovery' ExpectedValue = 0 OperationType = 'eq' } } } } } } Debug = @{ShowErrors = $false } } function Test-IMO { [alias('Test-ImoAD')] [CmdletBinding()] param([switch] $ReturnResults, [string[]] $ExludeDomains, [string[]] $ExludeDomainControllers, [switch] $ShowErrors) $global:ProgressPreference = 'SilentlyContinue' $global:ErrorActionPreference = 'Stop' $Script:TestResults = [System.Collections.Generic.List[PSCustomObject]]::new() $Script:TestimoConfiguration.Debug.ShowErrors = $ShowErrors $Script:TestimoConfiguration.Exclusions.Domains = $ExludeDomains $Script:TestimoConfiguration.Exclusions.DomainControllers = $ExludeDomainControllers if ($Script:TestimoConfiguration.Exclusions.Domains) { Out-Begin -Text 'Following Domains will be ignored' -Level 0 Out-Status -Status $null -Domain $Domain -DomainController $DomainController -ExtendedValue ($Script:TestimoConfiguration.Exclusions.Domains -join ', ') } if ($Script:TestimoConfiguration.Exclusions.DomainControllers) { Out-Begin -Text 'Following Domain Controllers will be ignored' -Level 0 Out-Status -Status $null -Domain $Domain -DomainController $DomainController -ExtendedValue ($Script:TestimoConfiguration.Exclusions.DomainControllers -join ', ') } $ForestInformation = Get-TestimoForest $null = Start-Testing -Scope 'Forest' -ForestInformation $ForestInformation { foreach ($Domain in $ForestInformation.Domains) { $DomainInformation = Get-TestimoDomain -Domain $Domain Start-Testing -Scope 'Domain' -Domain $Domain -DomainInformation $DomainInformation -ForestInformation $ForestInformation { $DomainControllers = Get-TestimoDomainControllers -Domain $Domain foreach ($DC in $DomainControllers) { Start-Testing -Scope 'DomainControllers' -Domain $Domain -DomainController $DC.Name -IsPDC $DC.IsPDC -DomainInformation $DomainInformation -ForestInformation ForestInformation } } } } if ($ReturnResults) { $Script:TestResults } } Export-ModuleMember -Function @('Test-IMO') -Alias @('Test-ImoAD') |