Testimo.psm1
$ComputersUnsupported = @{Enable = $true Source = @{Name = "Computers Unsupported" Data = { $Computers = Get-ADComputer -Filter { (operatingsystem -like "*xp*") -or (operatingsystem -like "*vista*") -or (operatingsystem -like "*Windows NT*") -or (operatingsystem -like "*2000*") -or (operatingsystem -like "*2003*") } -Property Name, OperatingSystem, OperatingSystemServicePack, lastlogontimestamp -Server $Domain $Computers | Select-Object Name, OperatingSystem, OperatingSystemServicePack, @{name = "lastlogontimestamp"; expression = { [datetime]::fromfiletime($_.lastlogontimestamp) } } } Details = [ordered] @{Area = 'Cleanup' Category = '' Severity = '' RiskLevel = 0 Description = 'Computers running an unsupported operating system.' Resolution = 'Upgrade or remove computers from Domain.' Resources = @() } ExpectedOutput = $false } } $ComputersUnsupportedMainstream = @{Enable = $true Source = @{Name = "Computers Unsupported Mainstream Only" Data = { $Computers = Get-ADComputer -Filter { (operatingsystem -like "*2008*") } -Property Name, OperatingSystem, OperatingSystemServicePack, lastlogontimestamp -Server $Domain $Computers | Select-Object Name, OperatingSystem, OperatingSystemServicePack, @{name = "lastlogontimestamp"; expression = { [datetime]::fromfiletime($_.lastlogontimestamp) } } } Details = [ordered] @{Area = 'Cleanup' Category = '' Severity = '' RiskLevel = 0 Description = 'Computers running an unsupported operating system, but with possibly Microsoft support.' Resolution = 'Consider upgrading computers running Windows Server 2008 or Windows Server 2008 R2 to a version that still offers mainstream support from Microsoft.' Resources = @() } ExpectedOutput = $false } } $DHCPAuthorized = @{Enable = $false Source = @{Name = "DHCP authorized in domain" Data = { $SearchBase = 'cn=configuration,{0}' -f $DomainInformation.DistinguishedName Get-ADObject -SearchBase $searchBase -Filter "objectclass -eq 'dhcpclass' -AND Name -ne 'dhcproot'" } Details = [ordered] @{Area = 'Configuration' Category = 'DHCP' Severity = '' RiskLevel = 0 Description = "" Resolution = '' Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{DHCPAuthorized = @{Enable = $true Name = 'At least 1 DHCP Server Authorized' Parameters = @{ExpectedCount = '1' OperationType = 'gt' } } } } $DNSForwaders = @{Enable = $true Source = @{Name = "DNS Forwarders" Data = { [Array] $Forwarders = Get-WinDnsServerForwarder -Domain $Domain -WarningAction SilentlyContinue if ($Forwarders.Count -gt 1) { Compare-MultipleObjects -Objects $Forwarders -FormatOutput -CompareSorted:$true -ExcludeProperty GatheredFrom -SkipProperties -Property 'IpAddress' -WarningAction SilentlyContinue } elseif ($Forwarders.Count -eq 0) { [PSCustomObject] @{Source = 'No forwarders set' Status = $false } } else { [PSCustomObject] @{Source = $Forwarders[0].IPAddress -join ', ' Status = $true } } } Details = [ordered] @{Area = 'Configuration' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{SameForwarders = @{Enable = $true Name = 'Same DNS Forwarders' Parameters = @{Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Source' } Description = 'DNS forwarders within one domain should have identical setup' } } } $DNSScavengingForPrimaryDNSServer = @{Enable = $true Source = @{Name = "DNS Scavenging - Primary DNS Server" Data = { Get-WinDnsServerScavenging -IncludeDomains $Domain } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{ScavengingCount = @{Enable = $true Name = 'Scavenging DNS Servers Count' Parameters = @{WhereObject = { $null -ne $_.ScavengingInterval -and $_.ScavengingInterval -ne 0 } ExpectedCount = 1 OperationType = 'eq' } Description = '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 = @{WhereObject = { $null -ne $_.ScavengingInterval -and $_.ScavengingInterval -ne 0 } Property = 'ScavengingInterval', 'Days' ExpectedValue = 7 OperationType = 'le' } } 'Scavenging State' = @{Enable = $true Name = 'Scavenging State' Parameters = @{WhereObject = { $null -ne $_.ScavengingInterval -and $_.ScavengingInterval -ne 0 } Property = 'ScavengingState' ExpectedValue = $true OperationType = 'eq' } Description = 'Scavenging State is responsible for enablement of scavenging for all new zones created.' RecommendedValue = $true DescriptionRecommended = 'It should be enabled so all new zones are subject to scavanging.' DefaultValue = $false } 'Last Scavenge Time' = @{Enable = $true Name = 'Last Scavenge Time' Parameters = @{WhereObject = { $null -ne $_.ScavengingInterval -and $_.ScavengingInterval -ne 0 } Property = 'LastScavengeTime' ExpectedValue = '(Get-Date).AddDays(-7)' OperationType = 'gt' } } } } $DnsZonesAging = @{Enable = $true Source = @{Name = "Aging primary DNS Zone" Data = { $PSWinDocumentationDNS = Import-PrivateModule PSWinDocumentation.DNS & $PSWinDocumentationDNS { param($Domain) $Zones = Get-WinDnsServerZones -ZoneName $Domain -Domain $Domain Compare-MultipleObjects -Objects $Zones -FormatOutput -CompareSorted:$true -ExcludeProperty GatheredFrom -SkipProperties -Property 'AgingEnabled' } $Domain } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{EnabledAgingEnabled = @{Enable = $true Name = 'Zone DNS aging should be enabled' Parameters = @{Property = 'Source' ExpectedValue = $true OperationType = 'eq' } Description = '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' } Description = 'Primary DNS zone should have aging enabled, on all DNS servers.' } } } $DNSZonesDomain0ADEL = @{Enable = $true Source = @{Name = "DomainDNSZones should have proper FSMO Owner (0ADEL)" Data = { $IdentityDomain = "CN=Infrastructure,DC=DomainDnsZones,$(($DomainInformation).DistinguishedName)" $FSMORoleOwner = (Get-ADObject -Identity $IdentityDomain -Properties fSMORoleOwner -Server $Domain) $FSMORoleOwner } Details = [ordered] @{Area = 'Configuration' Category = 'DNS' Severity = '' RiskLevel = 0 Description = "" Resolution = '' Resources = @('https://blogs.technet.microsoft.com/the_9z_by_chris_davis/2011/12/20/forestdnszones-or-domaindnszones-fsmo-says-the-role-owner-attribute-could-not-be-read/' 'https://support.microsoft.com/en-us/help/949257/error-message-when-you-run-the-adprep-rodcprep-command-in-windows-serv' 'https://social.technet.microsoft.com/Forums/en-US/8b4a7794-13b2-4ef0-90c8-16799e9fd529/orphaned-fsmoroleowner-entry-for-domaindnszones?forum=winserverDS') } } Tests = [ordered] @{DNSZonesDomain0ADEL = @{Enable = $true Name = 'DomainDNSZones should have proper FSMO Owner (0ADEL)' Parameters = @{ExpectedValue = '0ADEL:' Property = 'fSMORoleOwner' OperationType = 'notmatch' } } } } $DNSZonesForest0ADEL = @{Enable = $true Source = @{Name = "ForestDNSZones should have proper FSMO Owner (0ADEL)" Data = { $IdentityForest = "CN=Infrastructure,DC=ForestDnsZones,$(($DomainInformation).DistinguishedName)" $FSMORoleOwner = (Get-ADObject -Identity $IdentityForest -Properties fSMORoleOwner -Server $Domain) $FSMORoleOwner } Requirements = @{IsDomainRoot = $true } Details = [ordered] @{Area = 'Configuration' Category = 'DNS' Severity = '' RiskLevel = 0 Description = "" Resolution = '' Resources = @('https://blogs.technet.microsoft.com/the_9z_by_chris_davis/2011/12/20/forestdnszones-or-domaindnszones-fsmo-says-the-role-owner-attribute-could-not-be-read/' 'https://support.microsoft.com/en-us/help/949257/error-message-when-you-run-the-adprep-rodcprep-command-in-windows-serv' 'https://social.technet.microsoft.com/Forums/en-US/8b4a7794-13b2-4ef0-90c8-16799e9fd529/orphaned-fsmoroleowner-entry-for-domaindnszones?forum=winserverDS') } ExpectedOutput = $true } Tests = [ordered] @{DNSZonesForest0ADEL = @{Enable = $true Name = 'ForestDNSZones should have proper FSMO Owner (0ADEL)' Parameters = @{ExpectedValue = '0ADEL:' Property = 'fSMORoleOwner' OperationType = 'notmatch' } } } } $DomainFSMORoles = @{Enable = $true Source = @{Name = 'Roles availability' Data = { Test-ADRolesAvailability -Domain $Domain } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $true } 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' } } } } $DuplicateObjects = @{Enable = $true Source = @{Name = "Duplicate Objects: 0ACNF" Data = { Get-ADObject -LDAPFilter "(|(cn=*\0ACNF:*)(ou=*OACNF:*))" -SearchScope Subtree -Server $Domain } Implementation = { $CNF = Get-ADObject -LDAPFilter "(|(cn=*\0ACNF:*)(ou=*OACNF:*))" -SearchScope Subtree foreach ($_ in $CNF) { Remove-ADObject -Identity $_.ObjectGUID.Guid -Recursive } } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = "CNF objects are objects that are created when two or more objects with the same name are created on different domain controllers. When replication occurs the conflict is resolved by renaming the object with the older timestamp to a name with CNF in it's distinguished name." Resolution = '' Resources = @('https://jorgequestforknowledge.wordpress.com/2014/09/17/finding-conflicting-objects-in-your-ad/' 'https://social.technet.microsoft.com/Forums/en-US/e9327be6-922c-4b9f-8357-417c3ab6a1af/cnf-remove-from-ad?forum=winserverDS' 'https://ganeshnadarajanblog.wordpress.com/2017/12/18/find-cnf-objects-in-active-directory/' 'https://kickthatcomputer.wordpress.com/2014/11/22/seek-and-destroy-duplicate-ad-objects-with-cnf-in-the-name/' 'https://community.spiceworks.com/topic/2113346-active-directory-replication-cnf-guid-entries') } ExpectedOutput = $false } } $ExchangeUsers = @{Enable = $false Source = @{Name = "Exchange Users: Missing MailNickName" Data = { Get-ADUser -Filter { Mail -like '*' -and MailNickName -notlike '*' } -Properties mailNickName, mail -Server $Domain } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @('https://evotec.xyz/office-365-msexchhidefromaddresslists-does-not-synchronize-with-office-365/') } ExpectedOutput = $false } } $GroupPolicyADM = @{Enable = $true Source = @{Name = "Group Policy Legacy ADM Files" Data = { Get-GPOZaurrLegacyFiles -IncludeDomains $Domain } Implementation = { Remove-GPOZaurrLegacyFiles -Verbose -WhatIf } Details = [ordered] @{Area = 'Cleanup' Category = 'Group Policy' Severity = '' RiskLevel = 0 Description = "" Resolution = '' Resources = @('https://sdmsoftware.com/group-policy-blog/tips-tricks/understanding-the-role-of-admx-and-adm-files-in-group-policy/' 'https://social.technet.microsoft.com/Forums/en-US/bbbe04f5-218b-4526-ae67-cf82a20d49fc/deleting-adm-templates?forum=winserverGP' 'https://gallery.technet.microsoft.com/scriptcenter/Removing-ADM-files-from-b532e3b6#content') } ExpectedOutput = $false } } $GroupPolicyMissingPermissions = @{Enable = $true Source = @{Name = "Group Policy Missing Permissions" Data = { Get-WinADGPOMissingPermissions -Domain $Domain } Details = [ordered] @{Area = 'Security' Category = '' Severity = '' RiskLevel = 0 Description = "Group Policy permissions should always have Authenticated Users and Domain Computers gropup" Resolution = 'Do not remove Authenticated Users, Domain Computers from Group Policies.' Resources = @('https://secureinfra.blog/2018/12/31/most-common-mistakes-in-active-directory-and-domain-services-part-1/' 'https://support.microsoft.com/en-us/help/3163622/ms16-072-security-update-for-group-policy-june-14-2016') } ExpectedOutput = $false } } $GroupPolicyOwner = @{Enable = $true Source = @{Name = "GPO: Owner" Data = { Get-GPOZaurrOwner -IncludeSysvol -IncludeDomains $Domain } Details = [ordered] @{Area = 'Security' Category = '' Severity = '' RiskLevel = 0 Description = "" Resolution = '' Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{OwnerConsistent = @{Enable = $true Name = 'GPO: Owner Consistent' Parameters = @{WhereObject = { $_.IsOwnerConsistent -ne $true } ExpectedResult = $false } } OwnerAdministrative = @{Enable = $true Name = 'GPO: Owner Administrative' Parameters = @{WhereObject = { $_.OwnerType -ne 'Administrative' -or $_.SysvolType -ne 'Administrative' } ExpectedResult = $false } } } } $GroupPolicyPermissionConsistency = @{Enable = $true Source = @{Name = "GPO: Permission Consistency" Data = { Get-GPOZaurrPermissionConsistency -VerifyInheritance -Type Inconsistent -IncludeDomains $Domain } Details = [ordered] @{Area = 'Security' Category = '' Severity = '' RiskLevel = 0 Description = "GPO Permissions are stored in Active Directory and SYSVOL at the same time. Setting up permissions for GPO should replicate itself to SYSVOL and those permissions should be consistent. However, sometimes this doesn't happen or is done on purpose." Resolution = '' Resources = @() } ExpectedOutput = $false } } $GroupPolicyPermissionUnknown = @{Enable = $true Source = @{Name = "GPO: Permission Unknown" Data = { Get-GPOZaurrPermission -Type Unknown -IncludeDomains $Domain } Details = [ordered] @{Area = 'Cleanup' Category = '' Severity = '' RiskLevel = 0 Description = "There should be no unknown permissions (deleted users/groups/computers)." Resolution = '' Resources = @() } ExpectedOutput = $false } } $GroupPolicySysvol = @{Enable = $true Source = @{Name = "GPO: Sysvol folder existance" Data = { Get-GPOZaurrSysvol -IncludeDomains $Domain } Details = [ordered] @{Area = 'Security' Category = '' Severity = '' RiskLevel = 0 Description = "GPO Permissions are stored in Active Directory and SYSVOL at the same time. Sometimes when deleting GPO or due to replication issues GPO becomes orphaned (no SYSVOL files) or SYSVOL files exists but no GPO." Resolution = '' Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{SysvolExists = @{Enable = $true Name = 'GPO: Owner Consistent' Parameters = @{WhereObject = { $_.SysvolStatus -ne 'Exists' -or $_.Status -ne 'Exists' } ExpectedResult = $false } } } } $KerberosAccountAge = @{Enable = $true Source = @{Name = "Kerberos Account Age" Data = { Get-ADUser -Identity krbtgt -Properties Created, PasswordLastSet, msDS-KeyVersionNumber -Server $Domain } Details = [ordered] @{Area = 'Security' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $true } 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' } } } } $OrganizationalUnitsEmpty = @{Enable = $true Source = @{Name = "Orphaned/Empty Organizational Units" Data = { $OrganizationalUnits = Get-ADOrganizationalUnit -Filter * -Properties distinguishedname -Server $Domain | Select-Object -ExpandProperty distinguishedname $WellKnownContainers = Get-ADDomain | Select-Object *Container $AllUsedOU = Get-ADObject -Filter "ObjectClass -eq 'user' -or ObjectClass -eq 'computer' -or ObjectClass -eq 'group' -or ObjectClass -eq 'contact'" -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) -and (($_ -notlike $WellKnownContainers.UsersContainer) -or ($_ -notlike $WellKnownContainers.ComputersContainer)) } } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $false } } $OrganizationalUnitsProtected = @{Enable = $true Source = @{Name = "Orphaned/Empty Organizational Units" Data = { $OUs = Get-ADOrganizationalUnit -Properties ProtectedFromAccidentalDeletion, CanonicalName -Filter * -Server $Domain $FilteredOus = foreach ($OU in $OUs) { if ($OU.ProtectedFromAccidentalDeletion -eq $false) { $OU } } $FilteredOus | Select-Object -Property Name, CanonicalName, DistinguishedName, ProtectedFromAccidentalDeletion } Details = [ordered] @{Area = 'Cleanup' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $false } } $OrphanedForeignSecurityPrincipals = @{Enable = $true Source = @{Name = "Orphaned Foreign Security Principals" Data = { $AllFSP = Get-WinADUsersForeignSecurityPrincipalList -Domain $Domain $OrphanedObjects = $AllFSP | Where-Object { $_.TranslatedName -eq $null } $OrphanedObjects } Details = [ordered] @{Area = 'Cleanup' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $false } } $PasswordComplexity = @{Enable = $true Source = @{Name = 'Password Complexity Requirements' Data = { $ADModule = Import-PrivateModule PSWinDocumentation.AD & $ADModule { param($Domain); Get-WinADDomainDefaultPasswordPolicy -Domain $Domain } $Domain } Details = [ordered] @{Area = 'Security' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{ComplexityEnabled = @{Enable = $true Name = 'Complexity Enabled' Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } Parameters = @{Property = 'Complexity Enabled' ExpectedValue = $true OperationType = 'eq' } } 'LockoutDuration' = @{Enable = $true Name = 'Lockout Duration' Parameters = @{Property = 'Lockout Duration' ExpectedValue = 30 OperationType = 'ge' } } 'LockoutObservationWindow' = @{Enable = $true Name = 'Lockout Observation Window' Parameters = @{Property = 'Lockout Observation Window' ExpectedValue = 30 OperationType = 'ge' } } 'LockoutThreshold' = @{Enable = $true Name = 'Lockout Threshold' Parameters = @{Property = 'Lockout Threshold' ExpectedValue = 4 OperationType = 'gt' } } 'MaxPasswordAge' = @{Enable = $true Name = 'Max Password Age' Parameters = @{Property = 'Max Password Age' ExpectedValue = 60 OperationType = 'le' } } 'MinPasswordLength' = @{Enable = $true Name = 'Min Password Length' Parameters = @{Property = 'Min Password Length' ExpectedValue = 8 OperationType = 'gt' } } 'MinPasswordAge' = @{Enable = $true Name = 'Min Password Age' Parameters = @{Property = 'Min Password Age' ExpectedValue = 1 OperationType = 'le' } } 'PasswordHistoryCount' = @{Enable = $true Name = 'Password History Count' Parameters = @{Property = 'Password History Count' ExpectedValue = 10 OperationType = 'ge' } } 'ReversibleEncryptionEnabled' = @{Enable = $true Name = 'Reversible Encryption Enabled' Parameters = @{Property = 'Reversible Encryption Enabled' ExpectedValue = $false OperationType = 'eq' } } } } $SecurityGroupsAccountOperators = @{Enable = $true Source = @{Name = "Groups: Account operators should be empty" Data = { Get-ADGroupMember -Identity 'S-1-5-32-548' -Recursive -Server $Domain } Details = [ordered] @{Area = 'Cleanup', 'Security' Category = '' Severity = '' RiskLevel = 0 Description = "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!" Resolution = '' Resources = @() } ExpectedOutput = $false } } $SecurityGroupsSchemaAdmins = @{Enable = $true Source = @{Name = "Groups: Schema Admins should be empty" Data = { $DomainSID = (Get-ADDomain -Server $Domain).DomainSID Get-ADGroupMember -Recursive -Server $Domain -Identity "$DomainSID-518" } Requirements = @{IsDomainRoot = $true } Details = [ordered] @{Area = 'Cleanup', 'Security' Category = '' Severity = '' RiskLevel = 0 Description = "Schema Admins group should be empty. If you need to manage schema you can always add user for the time of modification." Resolution = 'Keep Schema group empty.' Resources = @('https://www.stigviewer.com/stig/active_directory_forest/2016-12-19/finding/V-72835') } ExpectedOutput = $false } } $SecurityKRBGT = @{Enable = $true Source = @{Name = "Security: Krbtgt password" Data = { Get-ADUser -Identity "krbtgt" -Property Name, Created, logonCount, Modified, PasswordLastSet, PasswordExpired, msDS-KeyVersionNumber, CanonicalName, msDS-KrbTgtLinkBl -Server $Domain } Details = [ordered] @{Area = 'Security', 'Cleanup' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @('https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/ad-forest-recovery-resetting-the-krbtgt-password' 'https://gallery.technet.microsoft.com/Reset-the-krbtgt-account-581a9e51' 'https://www.microsoft.com/security/blog/2015/02/11/krbtgt-account-password-reset-scripts-now-available-for-customers/') } ExpectedOutput = $true } Tests = [ordered] @{PasswordLastSet = @{Enable = $true Name = 'Krbtgt Last Password Change Should be less than 40 days ago' Parameters = @{Property = 'PasswordLastSet' ExpectedValue = '(Get-Date).AddDays(-40)' OperationType = 'gt' } Description = 'User account should be disabled or LastPasswordChange should be less than 1 year ago.' } } } $SecurityUsers = @{Enable = $true Source = @{Name = "Users: Standard" Data = { $GuestSID = "$((Get-ADDomain -Server $Domain).DomainSID.Value)-501" Get-ADUser -Filter { (AllowReversiblePasswordEncryption -eq $true -or UseDESKeyOnly -eq $true -or PrimaryGroupID -ne '513') -and (SID -ne $GuestSID) } -Properties AllowReversiblePasswordEncryption, UseDESKeyOnly, PrimaryGroup, PrimaryGroupID, PasswordLastSet, Enabled -Server $Domain } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $false } Tests = [ordered] @{KeberosDES = @{Enable = $true Name = 'Kerberos DES detection' Parameters = @{WhereObject = { $_.UseDESKeyOnly -eq $true } ExpectedCount = 0 OperationType = 'eq' ExpectedOutput = $false } Description = "User accounts shouldn't use DES encryption. Having UseDESKeyOnly forces the Kerberos encryption to be DES instead of RC4 which is the Microsoft default. DES is 56 bit encryption and is regarded as weak these days so this setting is not recommended." } AllowReversiblePasswordEncryption = @{Enable = $true Name = 'Reversible Password detection' Parameters = @{WhereObject = { $_.AllowReversiblePasswordEncryption -eq $true } ExpectedCount = 0 OperationType = 'eq' ExpectedOutput = $false } Description = "User accounts shouldn't use Reversible Password Encryption. Having AllowReversiblePasswordEncryption allows for easy password decryption." } PrimaryGroup = @{Enable = $true Name = "Primary Group shouldn't be changed from default Domain Users." Parameters = @{WhereObject = { $_.PrimaryGroupID -ne 513 -and $_.SID -ne "$((Get-ADDomain).DomainSID.Value)-501" } ExpectedCount = 0 OperationType = 'eq' ExpectedOutput = $false } Description = "User accounts shouldn't have different group then Domain Users as their primary group." } } } $SecurityUsersAcccountAdministrator = @{Enable = $true Source = @{Name = "Users: Administrator" Data = { $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 } } } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $true } 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' } Description = 'Administrator account should be disabled or LastPasswordChange should be less than 1 year ago.' } } } $SysVolDFSR = @{Enable = $true Source = @{Name = "DFSR Flags" Data = { $DistinguishedName = (Get-ADDomain -Server $Domain).DistinguishedName $ADObject = "CN=DFSR-GlobalSettings,CN=System,$DistinguishedName" $Object = Get-ADObject -Identity $ADObject -Properties * -Server $Domain if ($Object.'msDFSR-Flags' -gt 47) { [PSCustomObject] @{'SysvolMode' = 'DFS-R' 'Flags' = $Object.'msDFSR-Flags' } } else { [PSCustomObject] @{'SysvolMode' = 'Not DFS-R' 'Flags' = $Object.'msDFSR-Flags' } } } Details = [ordered] @{Area = 'Configuration' Category = '' Severity = '' RiskLevel = 0 Description = 'Checks if DFS-R is available.' Resolution = '' Resources = @('https://blogs.technet.microsoft.com/askds/2009/01/05/dfsr-sysvol-migration-faq-useful-trivia-that-may-save-your-follicles/' 'https://dirteam.com/sander/2019/04/10/knowledgebase-in-place-upgrading-domain-controllers-to-windows-server-2019-while-still-using-ntfrs-breaks-sysvol-replication-and-dslocator/') } ExpectedOutput = $true } Tests = [ordered] @{DFSRSysvolState = @{Enable = $true Name = 'DFSR Sysvol State' Parameters = @{Property = 'SysvolMode' ExpectedValue = 'DFS-R' OperationType = 'eq' PropertyExtendedValue = 'Flags' } } } } $Trusts = @{Enable = $true Source = @{Name = "Trust Availability" Data = { $ADModule = Import-PrivateModule PSWinDocumentation.AD & $ADModule { param($Domain) Get-WinADDomainTrusts -Domain $Domain } -Domain $Domain } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = 'Verifies if trusts are available and tests for trust unconstrained TGTDelegation' Resolution = '' Resources = @('https://blogs.technet.microsoft.com/askpfeplat/2019/04/11/changes-to-ticket-granting-ticket-tgt-delegation-across-trusts-in-windows-server-askpfeplat-edition/') } ExpectedOutput = $null } Tests = [ordered] @{TrustsConnectivity = @{Enable = $true Name = 'Trust status verification' Parameters = @{OverwriteName = { "Trust status verification | Source $Domain, Target $($_.'Trust Target'), Direction $($_.'Trust Direction')" } Property = 'Trust Status' ExpectedValue = 'OK' OperationType = 'eq' } } TrustsUnconstrainedDelegation = @{Enable = $true Name = 'Trust unconstrained TGTDelegation' Parameters = @{OverwriteName = { "Trust unconstrained TGTDelegation | Source $Domain, Target $($_.'Trust Target'), Direction $($_.'Trust Direction')" } WhereObject = { $($_.'Trust Direction' -eq 'BiDirectional' -or $_.'Trust Direction' -eq 'InBound') } Property = 'TGTDelegation' ExpectedValue = $True OperationType = 'eq' } } } } $WellKnownFolders = @{Enable = $true Source = @{Name = 'Well known folders' Data = { $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 } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = 'Verifies whether well-known folders are at their defaults or not.' Resolution = '' Resources = @() } ExpectedOutput = $true } 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' } } } } $DCDNSForwaders = @{Enable = $true Source = @{Name = "DC DNS Forwarders" Data = { $Forwarders = Get-WinDnsServerForwarder -Domain $Domain -IncludeDomainControllers $DomainController -WarningAction SilentlyContinue -Formatted $Forwarders } Details = [ordered] @{Area = 'Configuration' Category = 'DNS' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{SameForwarders = @{Enable = $true Name = 'Multiple DNS Forwarders' Parameters = @{Property = 'ForwardersCount' ExpectedValue = 1 OperationType = 'gt' PropertyExtendedValue = 'IPAddress' } Description = 'DNS: More than one forwarding server should be configured' } } } $DFS = @{Enable = $true Source = @{Name = "SYSVOL/DFS Verification" Data = { Get-WinADDFSHealth -Domains $Domain -DomainControllers $DomainController } Parameters = @{EventDays = 3 } Details = [ordered] @{Area = 'Health' Category = 'DFS' Severity = 'High' RiskLevel = 0 Description = "Provides health verification of SYSVOL/DFS on Domain Controller." Resolution = '' Resources = @('https://support.microsoft.com/en-us/help/2218556/how-to-force-an-authoritative-and-non-authoritative-synchronization-fo' 'https://www.itprotoday.com/windows-78/fixing-broken-sysvol-replication' 'https://www.brisk-it.net/when-dfs-replication-goes-wrong-and-how-to-fix-it/' 'https://gallery.technet.microsoft.com/scriptcenter/AD-DFS-Replication-Auto-812a88bc' 'https://www.reddit.com/r/sysadmin/comments/7gey4k/resuming_dfs_replication_after_4_years_of_no/' 'https://kimconnect.com/fix-dfs-replication-problems/' 'https://community.spiceworks.com/topic/2205945-repairing-broken-dfs-replication' 'https://support.microsoft.com/en-us/help/2958414/dfs-replication-how-to-troubleshoot-missing-sysvol-and-netlogon-shares' 'https://noobient.com/2013/11/11/fixing-sysvol-replication-on-windows-server-2012/' 'https://jackstromberg.com/2014/07/sysvol-and-group-policy-out-of-sync-on-server-2012-r2-dcs-using-dfsr/') } ExpectedOutput = $true } Tests = [ordered] @{Status = @{Enable = $true Name = 'DFS should be Healthy' Parameters = @{ExpectedValue = $true Property = 'Status' OperationType = 'eq' } } ReplicationState = @{Enable = $true Name = 'Replication State should be NORMAL' Parameters = @{ExpectedValue = 'Normal' Property = 'ReplicationState' OperationType = 'eq' } } CentralRepository = @{Enable = $true Name = 'Central Repository for GPO for Domain should be available' Parameters = @{ExpectedValue = $true Property = 'CentralRepository' OperationType = 'eq' } } CentralRepositoryDC = @{Enable = $true Name = 'Central Repository for GPO for DC should be available' Parameters = @{ExpectedValue = $true Property = 'CentralRepositoryDC' OperationType = 'eq' } } IdenticalCount = @{Enable = $true Name = 'GPO Count should match folder count' Parameters = @{ExpectedValue = $true Property = 'IdenticalCount' OperationType = 'eq' } } MemberReference = @{Enable = $true Name = 'MemberReference should return TRUE' Parameters = @{ExpectedValue = $true Property = 'MemberReference' OperationType = 'eq' } } DFSErrors = @{Enable = $true Name = 'DFSErrors should be 0' Parameters = @{ExpectedValue = 0 Property = 'DFSErrors' OperationType = 'eq' } } DFSLocalSetting = @{Enable = $true Name = 'DFSLocalSetting should be TRUE' Parameters = @{ExpectedValue = $true Property = 'DFSLocalSetting' OperationType = 'eq' } } DomainSystemVolume = @{Enable = $true Name = 'DomainSystemVolume should be TRUE' Parameters = @{ExpectedValue = $true Property = 'DomainSystemVolume' OperationType = 'eq' } } SYSVOLSubscription = @{Enable = $true Name = 'SYSVOLSubscription should be TRUE' Parameters = @{ExpectedValue = $true Property = 'SYSVOLSubscription' OperationType = 'eq' } } DFSRAutoRecovery = @{Enable = $true Name = 'DFSR AutoRecovery should be enabled (not stopped)' Parameters = @{Property = 'StopReplicationOnAutoRecovery' ExpectedValue = $false OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://secureinfra.blog/2019/04/30/field-notes-a-quick-tip-on-dfsr-automatic-recovery-while-you-prepare-for-an-ad-domain-upgrade/' 'https://richardjgreen.net/active-directory-dfs-r-auto-recovery/') } } } } $Diagnostics = @{Enable = $true Source = @{Name = 'Diagnostics (DCIAG)' Data = { Test-ADDomainController -ComputerName $DomainController -WarningAction SilentlyContinue } Details = [ordered] @{Area = 'Health' Category = 'OverallDCHealth' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://social.technet.microsoft.com/Forums/en-US/b48ee073-eb71-4852-8f56-ecf6f76b3fff/how-could-i-change-result-of-dcdiag-language-to-english-?forum=winserver8gen') } ExpectedOutput = $true } Tests = [ordered] @{Connectivity = @{Enable = $true Name = 'DCDiag Connectivity' Parameters = @{WhereObject = { $_.Test -eq 'Connectivity' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } Advertising = @{Enable = $true Name = 'DCDiag Advertising' Parameters = @{WhereObject = { $_.Test -eq 'Advertising' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } CheckSecurityError = @{Enable = $true Name = 'DCDiag CheckSecurityError' Parameters = @{WhereObject = { $_.Test -eq 'CheckSecurityError' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } CutoffServers = @{Enable = $true Name = 'DCDiag CutoffServers' Parameters = @{WhereObject = { $_.Test -eq 'CutoffServers' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } FrsEvent = @{Enable = $true Name = 'DCDiag FrsEvent' Parameters = @{WhereObject = { $_.Test -eq 'FrsEvent' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } DFSREvent = @{Enable = $true Name = 'DCDiag DFSREvent' Parameters = @{WhereObject = { $_.Test -eq 'DFSREvent' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } SysVolCheck = @{Enable = $true Name = 'DCDiag SysVolCheck' Parameters = @{WhereObject = { $_.Test -eq 'SysVolCheck' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } FrsSysVol = @{Enable = $true Name = 'DCDiag FrsSysVol' Parameters = @{WhereObject = { $_.Test -eq 'FrsSysVol' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } KccEvent = @{Enable = $true Name = 'DCDiag KccEvent' Parameters = @{WhereObject = { $_.Test -eq 'KccEvent' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } KnowsOfRoleHolders = @{Enable = $true Name = 'DCDiag KnowsOfRoleHolders' Parameters = @{WhereObject = { $_.Test -eq 'KnowsOfRoleHolders' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } MachineAccount = @{Enable = $true Name = 'DCDiag MachineAccount' Parameters = @{WhereObject = { $_.Test -eq 'MachineAccount' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } NCSecDesc = @{Enable = $true Name = 'DCDiag NCSecDesc' Parameters = @{WhereObject = { $_.Test -eq 'NCSecDesc' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } NetLogons = @{Enable = $true Name = 'DCDiag NetLogons' Parameters = @{WhereObject = { $_.Test -eq 'NetLogons' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } ObjectsReplicated = @{Enable = $true Name = 'DCDiag ObjectsReplicated' Parameters = @{WhereObject = { $_.Test -eq 'ObjectsReplicated' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } Replications = @{Enable = $true Name = 'DCDiag Replications' Parameters = @{WhereObject = { $_.Test -eq 'Replications' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } RidManager = @{Enable = $true Name = 'DCDiag RidManager' Parameters = @{WhereObject = { $_.Test -eq 'RidManager' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } Services = @{Enable = $true Name = 'DCDiag Services' Parameters = @{WhereObject = { $_.Test -eq 'Services' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } SystemLog = @{Enable = $true Name = 'DCDiag SystemLog' Parameters = @{WhereObject = { $_.Test -eq 'SystemLog' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } Topology = @{Enable = $true Name = 'DCDiag Topology' Parameters = @{WhereObject = { $_.Test -eq 'Topology' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } VerifyEnterpriseReferences = @{Enable = $true Name = 'DCDiag VerifyEnterpriseReferences' Parameters = @{WhereObject = { $_.Test -eq 'VerifyEnterpriseReferences' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } VerifyReferences = @{Enable = $true Name = 'DCDiag VerifyReferences' Parameters = @{WhereObject = { $_.Test -eq 'VerifyReferences' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } VerifyReplicas = @{Enable = $true Name = 'DCDiag VerifyReplicas' Parameters = @{WhereObject = { $_.Test -eq 'VerifyReplicas' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } DNS = @{Enable = $true Name = 'DCDiag DNS' Parameters = @{WhereObject = { $_.Test -eq 'DNS' -and $_.Target -ne $Domain } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } ForestDnsZonesCheckSDRefDom = @{Enable = $true Name = 'DCDiag ForestDnsZones CheckSDRefDom' Parameters = @{WhereObject = { $_.Test -eq 'CheckSDRefDom' -and $_.Target -eq 'ForestDnsZones' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } ForestDnsZonesCrossRefValidation = @{Enable = $true Name = 'DCDiag ForestDnsZones CrossRefValidation' Parameters = @{WhereObject = { $_.Test -eq 'CrossRefValidation' -and $_.Target -eq 'ForestDnsZones' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } DomainDnsZonesCheckSDRefDom = @{Enable = $true Name = 'DCDiag DomainDnsZones CheckSDRefDom' Parameters = @{WhereObject = { $_.Test -eq 'CheckSDRefDom' -and $_.Target -eq 'DomainDnsZones' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } DomainDnsZonesCrossRefValidation = @{Enable = $true Name = 'DCDiag DomainDnsZones CrossRefValidation' Parameters = @{WhereObject = { $_.Test -eq 'CrossRefValidation' -and $_.Target -eq 'DomainDnsZones' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } SchemaCheckSDRefDom = @{Enable = $true Name = 'DCDiag Schema CheckSDRefDom' Parameters = @{WhereObject = { $_.Test -eq 'CheckSDRefDom' -and $_.Target -eq 'Schema' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } SchemaCrossRefValidation = @{Enable = $true Name = 'DCDiag Schema CrossRefValidation' Parameters = @{WhereObject = { $_.Test -eq 'CrossRefValidation' -and $_.Target -eq 'Schema' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } ConfigurationCheckSDRefDom = @{Enable = $true Name = 'DCDiag Configuration CheckSDRefDom' Parameters = @{WhereObject = { $_.Test -eq 'CheckSDRefDom' -and $_.Target -eq 'Configuration' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } ConfigurationCrossRefValidation = @{Enable = $true Name = 'DCDiag Configuration CrossRefValidation' Parameters = @{WhereObject = { $_.Test -eq 'CrossRefValidation' -and $_.Target -eq 'Configuration' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } NetbiosCheckSDRefDom = @{Enable = $true Name = 'DCDiag NETBIOS CheckSDRefDom' Parameters = @{WhereObject = { $_.Test -eq 'CheckSDRefDom' -and ($_.Target -ne 'Configuration' -and $_.Target -ne 'ForestDnsZones' -and $_.Target -ne 'DomainDnsZones' -and $_.Target -ne 'Schema') } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } NetbiosCrossRefValidation = @{Enable = $true Name = 'DCDiag NETBIOS CrossRefValidation' Parameters = @{WhereObject = { $_.Test -eq 'CrossRefValidation' -and ($_.Target -ne 'Configuration' -and $_.Target -ne 'ForestDnsZones' -and $_.Target -ne 'DomainDnsZones' -and $_.Target -ne 'Schema') } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } DNSDomain = @{Enable = $true Name = 'DCDiag DNS' Parameters = @{WhereObject = { $_.Test -eq 'DNS' -and $_.Target -eq $Domain } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } LocatorCheck = @{Enable = $true Name = 'DCDiag LocatorCheck' Parameters = @{WhereObject = { $_.Test -eq 'LocatorCheck' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } FsmoCheck = @{Enable = $true Name = 'DCDiag FsmoCheck' Parameters = @{WhereObject = { $_.Test -eq 'FsmoCheck' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } Intersite = @{Enable = $true Name = 'DCDiag Intersite' Parameters = @{WhereObject = { $_.Test -eq 'Intersite' } Property = 'Result' ExpectedValue = $true OperationType = 'eq' } } } } $DiskSpace = @{Enable = $true Source = @{Name = 'Disk Free' Data = { Get-ComputerDiskLogical -ComputerName $DomainController -OnlyLocalDisk -WarningAction SilentlyContinue } Details = [ordered] @{Area = 'Health' Category = 'Disk' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{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' } } } } $DNSNameServers = @{Enable = $true Source = @{Name = "Name servers for primary domain zone" Data = { Test-DNSNameServers -Domain $Domain -DomainController $DomainController } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{DnsNameServersIdentical = @{Enable = $true Name = 'DNS Name servers for primary zone are identical' Parameters = @{Property = 'Status' ExpectedValue = $True OperationType = 'eq' PropertyExtendedValue = 'Comment' } Description = 'DNS Name servers for primary zone should be equal to Domain Controllers for a Domain.' } } } $DNSResolveExternal = @{Enable = $true Source = @{Name = "Resolves external DNS queries" Data = { $Output = Invoke-Command -ComputerName $DomainController -ErrorAction Stop { Resolve-DnsName -Name 'evotec.xyz' -ErrorAction SilentlyContinue } $Output } Details = [ordered] @{Area = 'Health' Category = 'DNS' Severity = 'High' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{ResolveDNSExternal = @{Enable = $true Name = 'Should resolve External DNS' Parameters = @{Property = 'IPAddress' ExpectedValue = '37.59.176.139' OperationType = 'eq' } Description = 'DNS should resolve external queries properly.' } } } $DNSResolveInternal = @{Enable = $true Source = @{Name = "Resolves internal DNS queries" Data = { $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 } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{ResolveDNSInternal = @{Enable = $true Name = 'Should resolve Internal DNS' Parameters = @{Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'IPAddresses' } Description = 'DNS should resolve internal domains correctly.' } } } $EventLogs = @{Enable = $true Source = @{Name = "Event Logs" Data = { Get-EventsInformation -LogName 'Application', 'System', 'Security', 'Microsoft-Windows-PowerShell/Operational' -Machine $DomainController -WarningAction SilentlyContinue } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{ApplicationLogMode = @{Enable = $true Name = 'Application Log mode is set to AutoBackup' Parameters = @{WhereObject = { $_.LogName -eq 'Application' } Property = 'LogMode' ExpectedValue = 'AutoBackup' OperationType = 'eq' } } ApplicationLogFull = @{Enable = $true Name = 'Application log is not full' Parameters = @{WhereObject = { $_.LogName -eq 'Application' } Property = 'IsLogFull' ExpectedValue = $false OperationType = 'eq' } } PowershellLogMode = @{Enable = $true Name = 'PowerShell Log mode is set to AutoBackup' Parameters = @{WhereObject = { $_.LogName -eq 'Microsoft-Windows-PowerShell/Operational' } Property = 'LogMode' ExpectedValue = 'AutoBackup' OperationType = 'eq' } } PowerShellLogFull = @{Enable = $true Name = 'PowerShell log is not full' Parameters = @{WhereObject = { $_.LogName -eq 'Microsoft-Windows-PowerShell/Operational' } Property = 'IsLogFull' ExpectedValue = $false OperationType = 'eq' } } SystemLogMode = @{Enable = $true Name = 'System Log mode is set to AutoBackup' Parameters = @{WhereObject = { $_.LogName -eq 'System' } Property = 'LogMode' ExpectedValue = 'AutoBackup' OperationType = 'eq' } } SystemLogFull = @{Enable = $true Name = 'System log is not full' Parameters = @{WhereObject = { $_.LogName -eq 'System' } Property = 'IsLogFull' ExpectedValue = $false OperationType = 'eq' } } SecurityLogMode = @{Enable = $true Name = 'Security Log mode is set to AutoBackup' Parameters = @{WhereObject = { $_.LogName -eq 'Security' } Property = 'LogMode' ExpectedValue = 'AutoBackup' OperationType = 'eq' } } SecurityLogFull = @{Enable = $true Name = 'Security log is not full' Parameters = @{WhereObject = { $_.LogName -eq 'Security' } Property = 'IsLogFull' ExpectedValue = $false OperationType = 'eq' } } SecurityMaximumLogSize = @{Enable = $true Name = 'Security Log Maximum Size smaller then 4GB' Parameters = @{WhereObject = { $_.LogName -eq 'Security' } Property = 'FileSizeMaximumMB' ExpectedValue = 4000 OperationType = 'le' } } SecurityCurrentLogSize = @{Enable = $true Name = 'Security Log Current Size smaller then 4GB' Parameters = @{WhereObject = { $_.LogName -eq 'Security' } Property = 'FileSizeCurrentMB' ExpectedValue = 4000 OperationType = 'le' } } SecurityPermissionsDefaultNetworkService = @{Enable = $true Name = 'Security Log has NT AUTHORITY\NETWORK SERVICE with AccessAllowed' Parameters = @{WhereObject = { $_.LogName -eq 'Security' -and $_.SecurityDescriptorDiscretionaryAcl -contains 'NT AUTHORITY\NETWORK SERVICE: AccessAllowed (ListDirectory)' } ExpectedCount = 1 OperationType = 'eq' } } SecurityPermissionsDefaultSYSTEM = @{Enable = $true Name = 'Security Log has NT AUTHORITY\SYSTEM with AccessAllowed' Parameters = @{WhereObject = { $_.LogName -eq 'Security' -and $_.SecurityDescriptorDiscretionaryAcl -contains 'NT AUTHORITY\SYSTEM: AccessAllowed (ChangePermissions, CreateDirectories, Delete, GenericExecute, ListDirectory, ReadPermissions, TakeOwnership)' } ExpectedCount = 1 OperationType = 'eq' } } SecurityPermissionsNDefaultBuiltinAdministrators = @{Enable = $true Name = 'Security Log has BUILTIN\Administrators with AccessAllowed' Parameters = @{WhereObject = { $_.LogName -eq 'Security' -and $_.SecurityDescriptorDiscretionaryAcl -contains 'BUILTIN\Administrators: AccessAllowed (CreateDirectories, ListDirectory)' } ExpectedCount = 1 OperationType = 'eq' } } SecurityPermissionsDefaultBuiltinEventLogReaders = @{Enable = $true Name = 'Security Log has BUILTIN\Event Log Readers with AccessAllowed' Parameters = @{WhereObject = { $_.LogName -eq 'Security' -and $_.SecurityDescriptorDiscretionaryAcl -contains 'BUILTIN\Event Log Readers: AccessAllowed (ListDirectory)' } ExpectedCount = 1 OperationType = 'eq' } } } } $FileSystem = @{Enable = $true Source = @{Name = "FileSystem" Data = { Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Control\FileSystem' -ComputerName $DomainController } Details = [ordered] @{Type = 'Security' Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @('') } Requirements = @{CommandAvailable = 'Get-WinADLMSettings' } ExpectedOutput = $true } Tests = [ordered] @{NtfsDisable8dot3NameCreation = @{Enable = $true Name = 'NtfsDisable8dot3NameCreation' Parameters = @{Property = 'NtfsDisable8dot3NameCreation' ExpectedValue = 0 OperationType = 'gt' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://guyrleech.wordpress.com/2014/04/15/ntfs-8-3-short-names-solving-the-issues/' 'https://blogs.technet.microsoft.com/josebda/2012/11/13/windows-server-2012-file-server-tip-disable-8-3-naming-and-strip-those-short-names-too/' 'https://support.microsoft.com/en-us/help/121007/how-to-disable-8-3-file-name-creation-on-ntfs-partitions') } } } } $GroupPolicySYSVOLDC = @{Enable = $true Source = @{Name = "Group Policy SYSVOL Verification" Data = { Get-GPOZaurrSysvol -IncludeDomains $Domain -IncludeDomainControllers $DomainController -VerifyDomainControllers | Where-Object { $_.SysvolStatus -ne 'Exists' -or $_.Status -ne 'Exists' } } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $false } } $Information = @{Enable = $true Source = @{Name = "Domain Controller Information" Data = { Get-ADDomainController -Server $DomainController } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{IsEnabled = @{Enable = $true Name = 'Is Enabled' Parameters = @{Property = 'Enabled' ExpectedValue = $True OperationType = 'eq' } } IsGlobalCatalog = @{Enable = $true Name = 'Is Global Catalog' Parameters = @{Property = 'IsGlobalCatalog' ExpectedValue = $True OperationType = 'eq' } } } } $LanManagerSettings = @{Enable = $true Source = @{Name = "Lan Manager Settings" Data = { Get-WinADLMSettings -DomainController $DomainController } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://adsecurity.org/?p=3377') } Requirements = @{CommandAvailable = 'Get-WinADLMSettings' } ExpectedOutput = $true } Tests = [ordered] @{Level = @{Enable = $true Name = 'LM Level' Parameters = @{Property = 'Level' ExpectedValue = 5 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } } AuditBaseObjects = @{Enable = $true Name = 'Audit Base Objects' Parameters = @{Property = 'AuditBaseObjects' ExpectedValue = $false OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpac/262a2bed-93d4-4c04-abec-cf06e9ec72fd') } } CrashOnAuditFail = @{Enable = $true Name = 'Crash On Audit Fail' Parameters = @{Property = 'CrashOnAuditFail' ExpectedValue = 0 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @('http://systemmanager.ru/win2k_regestry.en/46686.htm') } } EveryoneIncludesAnonymous = @{Enable = $true Name = 'Everyone Includes Anonymous' Parameters = @{Property = 'EveryoneIncludesAnonymous' ExpectedValue = $false OperationType = 'eq' } Details = [ordered] @{Title = 'Disable and Enforce the Setting "Network access: Let Everyone permissions apply to anonymous users"' Area = '' Description = 'This setting helps to prevent an unauthorized user could from anonymously listing account names and shared resources and use using the information to attempt to guess passwords, perform social engineering attacks, or launch DoS attacks.' Resolution = '' RiskLevel = 10 Resources = @('https://www.stigviewer.com/stig/windows_7/2014-04-02/finding/V-3377') } } SecureBoot = @{Enable = $true Name = 'Secure Boot' Parameters = @{Property = 'SecureBoot' ExpectedValue = $true OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } } LSAProtectionCredentials = @{Enable = $true Name = 'LSAProtectionCredentials' Parameters = @{Property = 'LSAProtectionCredentials' ExpectedValue = $true OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } } LimitBlankPasswordUse = @{Enable = $true Name = 'LimitBlankPasswordUse' Parameters = @{Property = 'LimitBlankPasswordUse' ExpectedValue = $true OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } } NoLmHash = @{Enable = $true Name = 'NoLmHash' Parameters = @{Property = 'NoLmHash' ExpectedValue = $true OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } } DisableDomainCreds = @{Enable = $true Name = 'DisableDomainCreds' Parameters = @{Property = 'DisableDomainCreds' ExpectedValue = $false OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://www.stigviewer.com/stig/windows_8/2014-01-07/finding/V-3376') } } ForceGuest = @{Enable = $true Name = 'ForceGuest' Parameters = @{Property = 'ForceGuest' ExpectedValue = $false OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } } } } $LanManServer = @{Enable = $true Source = @{Name = "Lan Man Server" Data = { Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanManServer\Parameters' -ComputerName $DomainController } Details = [ordered] @{Area = 'Security' Description = 'Lan Man Server' Resolution = '' RiskLevel = 10 Resources = @() } Requirements = @{CommandAvailable = 'Get-PSRegistry' } ExpectedOutput = $true } Tests = [ordered] @{DisableCompression = @{Enable = $false Name = 'Disable Compression SMBv3' Parameters = @{Property = 'DisableCompression' ExpectedValue = 1 OperationType = 'eq' } Details = [ordered] @{Area = 'Security' Description = 'Microsoft is aware of a remote code execution vulnerability in the way that the Microsoft Server Message Block 3.1.1 (SMBv3) protocol handles certain requests. An attacker who successfully exploited the vulnerability could gain the ability to execute code on the target SMB Server or SMB Client. To exploit the vulnerability against an SMB Server, an unauthenticated attacker could send a specially crafted packet to a targeted SMBv3 Server. To exploit the vulnerability against an SMB Client, an unauthenticated attacker would need to configure a malicious SMBv3 Server and convince a user to connect to it.' Resolution = 'Disable SMBv3 compression or apply patch. Since patch is available disabling is not nessecary.' RiskLevel = 10 Resources = @('https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/adv200005') } } EnableForcedLogoff = @{Enable = $true Name = 'Enable Forced Logoff' Parameters = @{Property = 'EnableForcedLogoff' ExpectedValue = 1 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'Users are not forcibly disconnected when logon hours expire.' Resolution = '' RiskLevel = 10 Resources = @('https://www.stigviewer.com/stig/windows_7/2012-07-02/finding/V-1136') } } EnableSecuritySignature = @{Enable = $true Name = 'Enable Security Signature' Parameters = @{Property = 'EnableSecuritySignature' ExpectedValue = 1 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'Microsoft network server: Digitally sign communications (if client agrees)' Resolution = '' RiskLevel = 10 Resources = @('https://support.microsoft.com/en-us/help/887429/overview-of-server-message-block-signing' 'https://community.spiceworks.com/topic/2131862-how-to-set-microsoft-network-server-digitally-sign-communications-always' 'https://www.stigviewer.com/stig/windows_server_2016/2017-11-20/finding/V-73663') } } RequireSecuritySignature = @{Enable = $true Name = 'Require Security Signature' Parameters = @{Property = 'RequireSecuritySignature' ExpectedValue = 1 OperationType = 'eq' } Details = [ordered] @{Type = 'Security' Area = '' Description = 'Microsoft network server: Digitally sign communications (always)' Vulnerability = 'Session hijacking uses tools that allow attackers who have access to the same network as the client computer or server to interrupt, end, or steal a session in progress. Attackers can potentially intercept and modify unsigned Server Message Block (SMB) packets and then modify the traffic and forward it so that the server might perform objectionable actions. Alternatively, the attacker could pose as the server or client after legitimate authentication and gain unauthorized access to data. SMB is the resource-sharing protocol that is supported by many Windows operating systems. It is the basis of NetBIOS and many other protocols. SMB signatures authenticate both users and the servers that host the data. If either side fails the authentication process, data transmission does not take place.' PotentialImpact = 'The Windows implementation of the SMB file and print-sharing protocol support mutual authentication, which prevents session hijacking attacks and supports message authentication to prevent man-in-the-middle attacks. SMB signing provides this authentication by placing a digital signature into each SMB, which is then verified by both the client computer and the server. Implementing SMB signing may negatively affect performance because each packet must be signed and verified. If these policy settings are enabled on a server that is performing multiple roles, such as a small business server that is serving as a domain controller, file server, print server, and application server, performance may be substantially slowed. Additionally, if you configure computers to ignore all unsigned SMB communications, older applications and operating systems cannot connect. However, if you completely disable all SMB signing, computers are vulnerable to session-hijacking attacks.' Resolution = '' RiskLevel = 10 Resources = @('https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/hh125918(v=ws.10)?redirectedfrom=MSDN#vulnerability' 'https://support.microsoft.com/en-us/help/887429/overview-of-server-message-block-signing' 'https://community.spiceworks.com/topic/2131862-how-to-set-microsoft-network-server-digitally-sign-communications-always') } } } } $LDAP = @{Enable = $true Source = @{Name = 'LDAP Connectivity' Data = { Test-LDAP -ComputerName $DomainController -WarningAction SilentlyContinue } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } 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' } } } } $LDAPInsecureBindings = @{Enable = $true Source = @{Name = 'LDAP Insecure Bindings' Data = { Get-WinADLDAPBindingsSummary -IncludeDomainControllers $DomainController -WarningAction SilentlyContinue } Details = [ordered] @{Area = 'Security' Category = 'LDAP' Description = 'LDAP channel binding and LDAP signing provide ways to increase the security of network communications between an Active Directory Domain Services (AD DS) or an Active Directory Lightweight Directory Services (AD LDS) and its clients. There is a vulerability in the default configuration for Lightweight Directory Access Protocol (LDAP) channel binding and LDAP signing and may expose Active directory domain controllers to elevation of privilege vulnerabilities. Microsoft Security Advisory ADV190023 address the issue by recommending the administrators enable LDAP channel binding and LDAP signing on Active Directory Domain Controllers. This hardening must be done manually until the release of the security update that will enable these settings by default.' Resolution = 'Make sure to remove any Clients performing simple or unsigned bindings.' RiskLevel = 10 Resources = @('https://docs.microsoft.com/en-us/archive/blogs/russellt/identifying-clear-text-ldap-binds-to-your-dcs' 'https://support.microsoft.com/en-us/help/4520412/2020-ldap-channel-binding-and-ldap-signing-requirement-for-windows') } ExpectedOutput = $false } Tests = [ordered] @{SimpleBinds = @{Enable = $true Name = 'Simple binds performed without SSL/TLS is 0' Parameters = @{Property = 'Number of simple binds performed without SSL/TLS' ExpectedValue = 0 OperationType = 'eq' } } UnsignedBinds = @{Enable = $true Name = 'Negotiate/Kerberos/NTLM/Digest binds performed without signing is 0' Parameters = @{Property = 'Number of Negotiate/Kerberos/NTLM/Digest binds performed without signing' ExpectedValue = 0 OperationType = 'eq' } } } } $MSSLegacy = @{Enable = $true Source = @{Name = "MSS (Legacy)" Data = { Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' -ComputerName $DomainController } Details = [ordered] @{Area = 'Security' Category = 'Network' Description = 'Provides verification of MSS Network Settings on Domain Controllers' Resolution = '' RiskLevel = 10 Resources = @('https://blogs.technet.microsoft.com/secguide/2016/10/02/the-mss-settings/') } Requirements = @{CommandAvailable = 'Get-PSRegistry' } ExpectedOutput = $true } Tests = [ordered] @{DisableIPSourceRouting = @{Enable = $true Name = 'DisableIPSourceRouting' Parameters = @{Property = 'DisableIPSourceRouting' ExpectedValue = 2 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'Highest protection, source routing is completely disabled' Resolution = '' RiskLevel = 10 Resources = @('https://blogs.technet.microsoft.com/secguide/2016/10/02/the-mss-settings/') } } EnableICMPRedirect = @{Enable = $true Name = 'EnableICMPRedirect' Parameters = @{Property = 'EnableICMPRedirect' ExpectedValue = 0 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://blogs.technet.microsoft.com/secguide/2016/10/02/the-mss-settings/') } } } } $NetSessionEnumeration = @{Enable = $true Source = @{Name = "Net Session Enumeration" Data = { $Registry = Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\DefaultSecurity" -ComputerName $DomainController $CSD = [System.Security.AccessControl.CommonSecurityDescriptor]::new($true, $false, $Registry.SrvsvcSessionInfo, 0) $CSD.DiscretionaryAcl.SecurityIdentifier | Where-Object { $_ -eq 'S-1-5-11' } } Details = [ordered] @{Type = 'Security' Area = '' Description = 'Net Session Enumeration is a method used to retrieve information about established sessions on a server. Any domain user can query a server for its established sessions.' Resolution = 'Hardening Net Session Enumeration' RiskLevel = 10 Resources = @('https://gallery.technet.microsoft.com/Net-Cease-Blocking-Net-1e8dcb5b') } Requirements = @{CommandAvailable = 'Get-PSRegistry' } ExpectedOutput = $false } } $NetworkCardSettings = @{Enable = $true Source = @{Name = "Get all network interfaces and firewall status" Data = { Get-ComputerNetwork -ComputerName $DomainController } Details = [ordered] @{Area = 'Connectivity' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{NETBIOSOverTCIP = @{Enable = $true Name = 'NetBIOS over TCIP should be disabled.' Parameters = @{Property = 'NetBIOSOverTCPIP' ExpectedValue = 'Disabled' OperationType = 'eq' } Details = @{Area = 'Connectivity' Category = 'Legacy Protocols' Severity = 'Critical' RiskLevel = 90 Description = @' NetBIOS over TCP/IP is a networking protocol that allows legacy computer applications relying on the NetBIOS to be used on modern TCP/IP networks. Enabling NetBios might help an attackers access shared directories, files and also gain sensitive information such as computer name, domain, or workgroup. '@ Resolution = 'Disable NetBIOS over TCPIP' Resources = @('http://woshub.com/how-to-disable-netbios-over-tcpip-and-llmnr-using-gpo/') } } Loopbackpresent = @{Enable = $true Name = 'Loopback IP address should be list in DNS servers on network card' Parameters = @{Property = 'DNSServerSearchOrder' ExpectedValue = '127.0.0.1' OperationType = 'Contains' } } WindowsFirewall = @{Enable = $true Name = 'Windows Firewall should be enabled on network card' Parameters = @{Property = 'FirewallStatus' ExpectedValue = $true OperationType = 'eq' } } WindowsFirewallProfile = @{Enable = $true Name = 'Windows Firewall should be set on domain network profile' Parameters = @{Property = 'FirewallProfile' ExpectedValue = 'DomainAuthenticated' OperationType = 'eq' } } DHCPDisabled = @{Enable = $false Name = 'DHCP should be disabled on network card' Parameters = @{Property = 'DHCPEnabled' ExpectedValue = $false OperationType = 'eq' } } } } $NTDSParameters = @{Enable = $true Source = @{Name = "NTDS Parameters" Data = { Get-PSRegistry -RegistryPath "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" -ComputerName $DomainController } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{DsaNotWritable = @{Enable = $true Name = 'Domain Controller should be writeable' Parameters = @{Property = 'Dsa Not Writable' ExpectedOutput = $false } } } } $OperatingSystem = @{Enable = $true Source = @{Name = 'Operating System' Data = { Get-ComputerOperatingSystem -ComputerName $DomainController -WarningAction SilentlyContinue } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } 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' } } } } $Pingable = @{Enable = $true Source = @{Name = 'Ping Connectivity' Data = { Test-NetConnection -ComputerName $DomainController -WarningAction SilentlyContinue } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{Ping = @{Enable = $true Name = 'Responding to PING' Parameters = @{Property = 'PingSucceeded' PropertyExtendedValue = 'PingReplyDetails', 'RoundtripTime' ExpectedValue = $true OperationType = 'eq' } } } } $Ports = [ordered] @{Enable = $true Source = [ordered] @{Name = 'TCP Ports are open/closed as required' Data = { $TcpPorts = @(53, 88, 135, 139, 389, 445, 464, 636, 3268, 3269, 9389) Test-ComputerPort -ComputerName $DomainController -PortTCP $TcpPorts -WarningAction SilentlyContinue } Requirements = @{CommandAvailable = 'Test-NetConnection' } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = '' Resolution = '' Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{Port53 = [ordered] @{Enable = $true Name = 'Port is OPEN' Parameters = @{WhereObject = { $_.Port -eq '53' } Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } Port88 = [ordered] @{Enable = $true Name = 'Port is OPEN' Parameters = @{WhereObject = { $_.Port -eq '88' } Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } Port135 = [ordered] @{Enable = $true Name = 'Port is OPEN' Parameters = @{WhereObject = { $_.Port -eq '135' } Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } Port139 = [ordered] @{Enable = $true Name = 'Port is CLOSED' Parameters = @{WhereObject = { $_.Port -eq '139' } Property = 'Status' ExpectedValue = $false OperationType = 'eq' PropertyExtendedValue = 'Summary' } Details = [ordered] @{Area = '' Category = '' Severity = '' RiskLevel = 0 Description = @' NetBIOS over TCP/IP is a networking protocol that allows legacy computer applications relying on the NetBIOS to be used on modern TCP/IP networks. Enabling NetBios might help an attackers access shared directories, files and also gain sensitive information such as computer name, domain, or workgroup. '@ Resolution = 'Disable NETBIOS over TCPIP' Resources = @('http://woshub.com/how-to-disable-netbios-over-tcpip-and-llmnr-using-gpo/') } } Port445 = [ordered] @{Enable = $true Name = 'Port is OPEN' Parameters = @{WhereObject = { $_.Port -eq '445' } Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } Port464 = [ordered] @{Enable = $true Name = 'Port is OPEN' Parameters = @{WhereObject = { $_.Port -eq '464' } Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } Port636 = [ordered] @{Enable = $true Name = 'Port is OPEN' Parameters = @{WhereObject = { $_.Port -eq '636' } Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } Port3268 = [ordered] @{Enable = $true Name = 'Port is OPEN' Parameters = @{WhereObject = { $_.Port -eq '3268' } Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } Port3269 = [ordered] @{Enable = $true Name = 'Port is OPEN' Parameters = @{WhereObject = { $_.Port -eq '3269' } Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } Port9389 = [ordered] @{Enable = $true Name = 'Port is OPEN' Parameters = @{WhereObject = { $_.Port -eq '9389' } Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } } } $RDPPorts = [ordered] @{Enable = $false Source = [ordered] @{Name = 'RDP Port is open' Data = { Test-ComputerPort -ComputerName $DomainController -PortTCP 3389 -WarningAction SilentlyContinue } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{PortOpen = [ordered] @{Enable = $false Name = 'Port is OPEN' Parameters = @{Property = 'Status' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'Summary' } } } } $RDPSecurity = [ordered] @{Enable = $true Source = [ordered] @{Name = 'RDP Security' Data = { Get-ComputerRDP -ComputerName $DomainController -WarningAction SilentlyContinue } Details = [ordered] @{Area = 'Connectivity' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{PortOpen = [ordered] @{Enable = $true Name = 'Port is OPEN' Parameters = @{Property = 'Connectivity' ExpectedValue = $true OperationType = 'eq' PropertyExtendedValue = 'ConnectivitySummary' } Details = [ordered] @{Area = 'Connectivity' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://lazywinadmin.com/2014/04/powershell-getset-network-level.html' 'https://devblogs.microsoft.com/scripting/weekend-scripter-report-on-network-level-authentication/') } } NLAAuthenticationEnabled = [ordered] @{Enable = $true Name = 'NLA Authentication is Enabled' Parameters = @{Property = 'UserAuthenticationRequired' ExpectedValue = $true OperationType = 'eq' } Details = [ordered] @{Area = 'Connectivity' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://lazywinadmin.com/2014/04/powershell-getset-network-level.html' 'https://devblogs.microsoft.com/scripting/weekend-scripter-report-on-network-level-authentication/') } } MinimalEncryptionLevel = [ordered] @{Enable = $true Name = 'Minimal Encryption Level is set to at least High' Parameters = @{Property = 'MinimalEncryptionLevelValue' ExpectedValue = 3 OperationType = 'ge' PropertyExtendedValue = 'MinimalEncryptionLevel' } Details = [ordered] @{Area = 'Connectivity' Description = 'Remote connections must be encrypted to prevent interception of data or sensitive information. Selecting "High Level" will ensure encryption of Remote Desktop Services sessions in both directions.' Resolution = '' RiskLevel = 10 Resources = @('https://www.stigviewer.com/stig/windows_server_2012_member_server/2014-01-07/finding/V-3454') } } } } $Services = [ordered] @{Enable = $true Source = @{Name = 'Service Status' Data = { $Services = @('ADWS', 'DNS', 'DFS', 'DFSR', 'Eventlog', 'EventSystem', 'KDC', 'LanManWorkstation', 'LanManServer', 'NetLogon', 'NTDS', 'RPCSS', 'SAMSS', 'Spooler', 'W32Time', 'XblGameSave', 'XblAuthManager') Get-PSService -Computers $DomainController -Services $Services } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } 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' ExpectedOutput = $false } Details = [ordered] @{Area = 'Security' Category = 'Services' Severity = '' RiskLevel = 0 Description = 'Due to security concerns SPOOLER should be disabled and stopped. However in some cases it may be required to have SPOOLER service up and running to cleanup stale printer objects from AD.' Resolution = '' Resources = @('https://adsecurity.org/?p=4056' 'https://docs.microsoft.com/en-us/windows-server/security/windows-services/security-guidelines-for-disabling-system-services-in-windows-server#print-spooler') } } SpoolerServiceStartType = @{Enable = $true Name = 'Spooler Service START TYPE is DISABLED' Parameters = @{WhereObject = { $_.Name -eq 'Spooler' } Property = 'StartType' ExpectedValue = 'Disabled' OperationType = 'eq' ExpectedOutput = $false } Details = [ordered] @{Area = 'Security' Category = 'Services' Severity = '' RiskLevel = 0 Description = 'Due to security concerns SPOOLER should be disabled and stopped. However in some cases it may be required to have SPOOLER service up and running to cleanup stale printer objects from AD.' Resolution = '' Resources = @('https://adsecurity.org/?p=4056' 'https://docs.microsoft.com/en-us/windows-server/security/windows-services/security-guidelines-for-disabling-system-services-in-windows-server#print-spooler') } } 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' } } XblAuthManagerServiceStatus = @{Enable = $true Name = 'XblAuthManager Service is STOPPED' Parameters = @{WhereObject = { $_.Name -eq 'XblAuthManager' } Property = 'Status' ExpectedValue = 'Stopped', 'N/A' OperationType = 'in' ExpectedOutput = $false } } XblAuthManagerStartupType = @{Enable = $true Name = 'XblAuthManager Service START TYPE is Disabled' Parameters = @{WhereObject = { $_.Name -eq 'XblAuthManager' } Property = 'StartType' ExpectedValue = 'Disabled', 'N/A' OperationType = 'in' ExpectedOutput = $false } } XblGameSaveServiceStatus = @{Enable = $true Name = 'XblGameSave Service is STOPPED' Parameters = @{WhereObject = { $_.Name -eq 'XblGameSave' } Property = 'Status' ExpectedValue = 'Stopped', 'N/A' OperationType = 'in' ExpectedOutput = $false } } XblGameSaveStartupType = @{Enable = $true Name = 'XblGameSave Service START TYPE is Disabled' Parameters = @{WhereObject = { $_.Name -eq 'XblGameSave' } Property = 'StartType' ExpectedValue = 'Disabled', 'N/A' OperationType = 'in' ExpectedOutput = $false } } } } $ServiceWINRM = @{Enable = $true Source = @{Name = "Service WINRM" Data = { Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WinRM\Service' -ComputerName $DomainController } Details = [ordered] @{Type = 'Security' Area = '' Description = 'Storage of administrative credentials could allow unauthorized access. Disallowing the storage of RunAs credentials for Windows Remote Management will prevent them from being used with plug-ins. The Windows Remote Management (WinRM) service must not store RunAs credentials.' Resolution = '' RiskLevel = 10 Resources = @() } Requirements = @{CommandAvailable = 'Get-PSRegistry' } ExpectedOutput = $true } Tests = [ordered] @{DisableRunAs = @{Enable = $true Name = 'DisableRunAs' Parameters = @{Property = 'DisableRunAs' ExpectedValue = 1 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'Storage of administrative credentials could allow unauthorized access. Disallowing the storage of RunAs credentials for Windows Remote Management will prevent them from being used with plug-ins. The Windows Remote Management (WinRM) service must not store RunAs credentials.' Resolution = '' RiskLevel = 10 Resources = @('https://www.stigviewer.com/stig/windows_server_2016/2018-03-07/finding/V-73603') } } } } $SMBProtocols = @{Enable = $true Source = @{Name = 'SMB Protocols' Data = { Get-ComputerSMB -ComputerName $DomainController } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://community.spiceworks.com/topic/2153374-bpa-on-windows-server-2016-warns-about-smb-not-in-a-default-configuration') } Requirements = @{CommandAvailable = 'Get-ComputerSMB' } ExpectedOutput = $true } Tests = [ordered] @{AsynchronousCredits = @{Enable = $true Name = 'AsynchronousCredits' Parameters = @{Property = 'AsynchronousCredits' ExpectedValue = 64 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'AsynchronousCredits should have the recommended value' Resolution = '' RiskLevel = 10 Resources = @() } } AutoDisconnectTimeout = @{Enable = $true Name = 'AutoDisconnectTimeout' Parameters = @{Property = 'AutoDisconnectTimeout' ExpectedValue = 0 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'AutoDisconnectTimeout should have the recommended value' Resolution = '' RiskLevel = 10 Resources = @() } } CachedOpenLimit = @{Enable = $true Name = 'CachedOpenLimit' Parameters = @{Property = 'CachedOpenLimit' ExpectedValue = 5 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'CachedOpenLimit should have the recommended value' Resolution = '' RiskLevel = 10 Resources = @() } } DurableHandleV2TimeoutInSeconds = @{Enable = $true Name = 'DurableHandleV2TimeoutInSeconds' Parameters = @{Property = 'DurableHandleV2TimeoutInSeconds' ExpectedValue = 30 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'DurableHandleV2TimeoutInSeconds should have the recommended value' Resolution = '' RiskLevel = 10 Resources = @() } } EnableSMB1Protocol = @{Enable = $true Name = 'SMB v1 Protocol should be disabled' Parameters = @{Property = 'EnableSMB1Protocol' ExpectedValue = $false OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } } EnableSMB2Protocol = @{Enable = $true Name = 'SMB v2 Protocol should be enabled' Parameters = @{Property = 'EnableSMB2Protocol' ExpectedValue = $true OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } } MaxThreadsPerQueue = @{Enable = $true Name = 'MaxThreadsPerQueue' Parameters = @{Property = 'MaxThreadsPerQueue' ExpectedValue = 20 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'MaxThreadsPerQueue should have the recommended value' Resolution = '' RiskLevel = 10 Resources = @() } } Smb2CreditsMin = @{Enable = $true Name = 'Smb2CreditsMin' Parameters = @{Property = 'Smb2CreditsMin' ExpectedValue = 128 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'Smb2CreditsMin should have the recommended value' Resolution = '' RiskLevel = 10 Resources = @() } } Smb2CreditsMax = @{Enable = $true Name = 'Smb2CreditsMax' Parameters = @{Property = 'Smb2CreditsMax' ExpectedValue = 2048 OperationType = 'eq' } Details = [ordered] @{Area = '' Description = 'Smb2CreditsMax should have the recommended value' Resolution = '' RiskLevel = 10 Resources = @('https://github.com/EvotecIT/Testimo/issues/50') } } RequireSecuritySignature = @{Enable = $true Name = 'SMB v2 Require Security Signature' Parameters = @{Property = 'RequireSecuritySignature' ExpectedValue = $true OperationType = 'eq' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } } } } $SMBShares = @{Enable = $true Source = @{Name = 'Default SMB Shares' Data = { Get-ComputerSMBShare -ComputerName $DomainController } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } Requirements = @{CommandAvailable = 'Get-ComputerSMBShare' } ExpectedOutput = $true } Tests = [ordered] @{AdminShare = @{Enable = $true Name = 'Remote Admin Share is available' Parameters = @{WhereObject = { $_.Name -eq 'ADMIN$' } ExpectedCount = 1 PropertyExtendedValue = 'Path' } } DefaultShare = @{Enable = $true Name = 'Default Share is available' Parameters = @{WhereObject = { $_.Name -eq 'C$' } ExpectedCount = 1 PropertyExtendedValue = 'Path' } } RemoteIPC = @{Enable = $true Name = 'Remote IPC Share is available' Parameters = @{WhereObject = { $_.Name -eq 'IPC$' } ExpectedCount = 1 PropertyExtendedValue = 'Path' } } NETLOGON = @{Enable = $true Name = 'NETLOGON Share is available' Parameters = @{WhereObject = { $_.Name -eq 'NETLOGON' } ExpectedCount = 1 PropertyExtendedValue = 'Path' } } SYSVOL = @{Enable = $true Name = 'SYSVOL Share is available' Parameters = @{WhereObject = { $_.Name -eq 'SYSVOL' } ExpectedCount = 1 PropertyExtendedValue = 'Path' } } } } $SMBSharesPermissions = @{Enable = $true Source = @{Name = 'Default SMB Shares Permissions' Data = { Get-ComputerSMBSharePermissions -ComputerName $DomainController -ShareName 'Netlogon', 'Sysvol' } Details = [ordered] @{Area = 'Security' Description = "SMB Shares for Sysvol and Netlogon should be at their defaults. That means 2 permissions for Netlogon and 3 for SysVol." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 20 Resources = @() } Requirements = @{CommandAvailable = 'Get-ComputerSMBSharePermissions' } ExpectedOutput = $true } Tests = [ordered] @{OverallCount = @{Enable = $true Name = 'Should only have default number of permissions' Parameters = @{ExpectedCount = 5 } Details = [ordered] @{Area = 'Security' Description = "SMB Shares for Sysvol and Netlogon should be at their defaults. That means 2 permissions for Netlogon and 3 for SysVol." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } } NetlogonEveryone = @{Enable = $true Name = 'Netlogon Share Permissions - Everyone' Parameters = @{WhereObject = { $_.Name -eq 'NETLOGON' -and $_.AccountName -eq 'Everyone' } ExpectedCount = 1 } Area = 'Security' Description = "SMB Shares for NETLOGON should contain Everyone with Read access rights." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } NetlogonAdministrators = @{Enable = $true Name = 'Netlogon Share Permissions - BUILTIN\Administrators' Parameters = @{WhereObject = { $_.Name -eq 'NETLOGON' -and $_.AccountName -eq 'BUILTIN\Administrators' } ExpectedCount = 1 } Area = 'Security' Description = "SMB Shares for NETLOGON should contain BUILTIN\Administrators with Full access rights." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } SysvolEveryone = @{Enable = $true Name = 'SysVol Share Permissions - Everyone' Parameters = @{WhereObject = { $_.Name -eq 'SYSVOL' -and $_.AccountName -eq 'Everyone' } ExpectedCount = 1 } Area = 'Security' Description = "SMB Shares for SYSVOL should contain Everyone with Read access rights." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } SysvolAdministrators = @{Enable = $true Name = 'SysVol Share Permissions - BUILTIN\Administrators' Parameters = @{WhereObject = { $_.Name -eq 'SYSVOL' -and $_.AccountName -eq 'BUILTIN\Administrators' } ExpectedCount = 1 } Area = 'Security' Description = "SMB Shares for SYSVOL should contain BUILTIN\Administrators with Full access rights." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } SysvolAuthenticatedUsers = @{Enable = $true Name = 'SysVol Share Permissions - NT AUTHORITY\Authenticated Users' Parameters = @{WhereObject = { $_.Name -eq 'SYSVOL' -and $_.AccountName -eq 'NT AUTHORITY\Authenticated Users' } ExpectedCount = 1 } Area = 'Security' Description = "SMB Shares for SYSVOL should contain NT AUTHORITY\Authenticated Users with Full access rights." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } NetlogonEveryoneValue = @{Enable = $true Name = 'Netlogon Share Permissions Value - Everyone' Parameters = @{WhereObject = { $_.Name -eq 'NETLOGON' -and $_.AccountName -eq 'Everyone' } Property = 'AccessRight' ExpectedValue = 'Read' OperationType = 'eq' } Area = 'Security' Description = "SMB Shares for NETLOGON should contain Everyone with Read access rights." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } NetlogonAdministratorsValue = @{Enable = $true Name = 'Netlogon Share Permissions Value - BUILTIN\Administrators' Parameters = @{WhereObject = { $_.Name -eq 'NETLOGON' -and $_.AccountName -eq 'BUILTIN\Administrators' } Property = 'AccessRight' ExpectedValue = 'Full' OperationType = 'eq' } Area = 'Security' Description = "SMB Shares for NETLOGON should contain BUILTIN\Administrators with Full access rights." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } SysvolEveryoneValue = @{Enable = $true Name = 'SysVol Share Permissions Value - Everyone' Parameters = @{WhereObject = { $_.Name -eq 'SYSVOL' -and $_.AccountName -eq 'Everyone' } Property = 'AccessRight' ExpectedValue = 'Read' OperationType = 'eq' } Area = 'Security' Description = "SMB Shares for SYSVOL should contain Everyone with Read access rights." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } SysvolAdministratorsValue = @{Enable = $true Name = 'SysVol Share Permissions Value - BUILTIN\Administrators' Parameters = @{WhereObject = { $_.Name -eq 'SYSVOL' -and $_.AccountName -eq 'BUILTIN\Administrators' } Property = 'AccessRight' ExpectedValue = 'Full' OperationType = 'eq' } Area = 'Security' Description = "SMB Shares for SYSVOL should contain BUILTIN\Administrators with Full access rights." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } SysvolAuthenticatedUsersValue = @{Enable = $true Name = 'SysVol Share Permissions Value - NT AUTHORITY\Authenticated Users' Parameters = @{WhereObject = { $_.Name -eq 'SYSVOL' -and $_.AccountName -eq 'NT AUTHORITY\Authenticated Users' } Property = 'AccessRight' ExpectedValue = 'Full' OperationType = 'eq' } Area = 'Security' Description = "SMB Shares for SYSVOL should contain NT AUTHORITY\Authenticated Users with Full access rights." Resolution = 'Add/Remove unnessecary permissions.' RiskLevel = 5 Resources = @() } } } $TimeSettings = [ordered] @{Enable = $true Source = @{Name = "Time Settings" Data = { Get-TimeSetttings -ComputerName $DomainController -Domain $Domain } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{NTPServerEnabled = @{Enable = $true Name = 'NtpServer must be enabled.' Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController } Property = 'NtpServerEnabled' ExpectedValue = $true OperationType = 'eq' } } NTPServerIntervalMissing = @{Enable = $true Name = 'Ntp Server Interval should be set' Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController } Property = 'NtpServerIntervals' ExpectedValue = 'Missing' OperationType = 'notcontains' } } NTPServerIntervalIncorrect = @{Enable = $true Name = 'Ntp Server Interval should be within known settings' Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController } Property = 'NtpServerIntervals' ExpectedValue = 'Incorrect' OperationType = 'notcontains' } } 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' } } } } $TimeSynchronizationExternal = @{Enable = $true Source = @{Name = "Time Synchronization External" Data = { Get-ComputerTime -TimeTarget $DomainController -WarningAction SilentlyContinue @SourceParameters } Parameters = @{TimeSource = 'pool.ntp.org' } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } 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' } $TimeSynchronizationInternal = @{Enable = $true Source = @{Name = "Time Synchronization Internal" Data = { Get-ComputerTime -TimeTarget $DomainController -WarningAction SilentlyContinue } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @('https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc773263(v=ws.10)#w2k3tr_times_tools_uhlp') } ExpectedOutput = $true } Tests = [ordered] @{LastBootUpTime = @{Enable = $true Name = 'Last Boot Up time should be less than X days' Parameters = @{Property = 'LastBootUpTime' ExpectedValue = '(Get-Date).AddDays(-60)' OperationType = 'gt' } } TimeSynchronizationTest = @{Enable = $true Name = 'Time Difference' Parameters = @{Property = 'TimeDifferenceSeconds' ExpectedValue = 1 OperationType = 'le' PropertyExtendedValue = 'TimeDifferenceSeconds' } } } } $UNCHardenedPaths = @{Enable = $true Source = @{Name = "Hardened UNC Paths" Data = { Get-PSRegistry -RegistryPath "HKLM\SOFTWARE\Policies\Microsoft\Windows\NetworkProvider\HardenedPaths" -ComputerName $DomainController } Details = [ordered] @{Type = 'Security' Area = '' Description = 'Hardened UNC Paths must be defined to require mutual authentication and integrity for at least the \\*\SYSVOL and \\*\NETLOGON shares.' Resolution = 'Harden UNC Paths for SYSVOL and NETLOGON' RiskLevel = 10 Resources = @('https://docs.microsoft.com/en-us/archive/blogs/leesteve/demystifying-the-unc-hardening-dilemma' 'https://www.stigviewer.com/stig/windows_10/2016-06-24/finding/V-63577' 'https://support.microsoft.com/en-us/help/3000483/ms15-011-vulnerability-in-group-policy-could-allow-remote-code-executi') } Requirements = @{CommandAvailable = 'Get-PSRegistry' } Implementation = {} Rollback = { Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\NetworkProvider\HardenedPaths" -Name "*" } ExpectedOutput = $true } Tests = [ordered] @{NetLogonUNCPath = @{Enable = $true Name = 'Netlogon UNC Hardening' Parameters = @{Property = '\\*\NETLOGON' ExpectedValue = 'RequireMutualAuthentication=1, RequireIntegrity=1' OperationType = 'eq' } Description = "Hardened UNC Paths must be defined to require mutual authentication and integrity for at least the \\*\SYSVOL and \\*\NETLOGON shares." } SysVolUNCPath = @{Enable = $true Name = 'SysVol UNC Hardening' Parameters = @{Property = '\\*\SYSVOL' ExpectedValue = 'RequireMutualAuthentication=1, RequireIntegrity=1' OperationType = 'eq' } Description = "Hardened UNC Paths must be defined to require mutual authentication and integrity for at least the \\*\SYSVOL and \\*\NETLOGON shares." } } } $WindowsRemoteManagement = @{Enable = $true Source = @{Name = 'Windows Remote Management' Data = { Test-WinRM -ComputerName $DomainController } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{WindowsRemoteManagement = @{Enable1 = $true Name = 'Test submits an identification request that determines whether the WinRM service is running.' Parameters = @{Property = 'Status' ExpectedValue = $true OperationType = 'eq' } } } } $WindowsRolesAndFeatures = @{Enable = $true Source = @{Name = "Windows Roles and Features" Data = { Get-WindowsFeature -ComputerName $DomainController } ExpectedOutput = $true } Tests = [ordered] @{ActiveDirectoryDomainServices = @{Enable = $true Name = 'Active Directory Domain Services is installed' Parameters = @{WhereObject = { $_.Name -eq 'AD-Domain-Services' } Property = 'Installed' ExpectedValue = $true OperationType = 'eq' } } DNSServer = @{Enable = $true Name = 'DNS Server is installed' Parameters = @{WhereObject = { $_.Name -eq 'DNS' } Property = 'Installed' ExpectedValue = $true OperationType = 'eq' } } FileandStorageServices = @{Enable = $true Name = 'File and Storage Services is installed' Parameters = @{WhereObject = { $_.Name -eq 'FileAndStorage-Services' } Property = 'Installed' ExpectedValue = $true OperationType = 'eq' } } FileandiSCSIServices = @{Enable = $true Name = 'File and iSCSI Services is installed' Parameters = @{WhereObject = { $_.Name -eq 'File-Services' } Property = 'Installed' ExpectedValue = $true OperationType = 'eq' } } FileServer = @{Enable = $true Name = 'File Server is installed' Parameters = @{WhereObject = { $_.Name -eq 'FS-FileServer' } Property = 'Installed' ExpectedValue = $true OperationType = 'eq' } } StorageServices = @{Enable = $true Name = 'Storage Services is installed' Parameters = @{WhereObject = { $_.Name -eq 'Storage-Services' } Property = 'Installed' ExpectedValue = $true OperationType = 'eq' } } WindowsPowerShell51 = @{Enable = $true Name = 'Windows PowerShell 5.1 is installed' Parameters = @{WhereObject = { $_.Name -eq 'PowerShell' } Property = 'Installed' ExpectedValue = $true OperationType = 'eq' } } } } $WindowsUpdates = @{Enable = $true Source = @{Name = "Windows Updates" Data = { Get-HotFix -ComputerName $DomainController | Sort-Object -Property InstalledOn -Descending | Select-Object -First 1 } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{WindowsUpdates = @{Enable = $true Name = 'Last Windows Updates should be less than X days ago' Parameters = @{Property = 'InstalledOn' ExpectedValue = '(Get-Date).AddDays(-60)' OperationType = 'gt' } } } } $Backup = @{Enable = $true Source = @{Name = 'Forest Backup' Data = { Get-WinADLastBackup -Forest $ForestName } Details = [ordered] @{Area = 'Backup' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{LastBackupTests = @{Enable = $true Name = 'Forest Last Backup Time - Context' Parameters = @{ExpectedValue = 2 OperationType = 'lt' Property = 'LastBackupDaysAgo' PropertyExtendedValue = 'LastBackup' OverwriteName = { "Last Backup $($_.NamingContext)" } } } } } $ForestFSMORoles = @{Enable = $true Source = @{Name = 'Roles availability' Data = { Test-ADRolesAvailability -Forest $ForestName } Details = [ordered] @{Area = 'Features' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } 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' } } } } $ObjectsWithConflict = @{Enable = $true Source = @{Name = 'Objects with Conflict (Duplicate RDN)' Data = { Get-WinADForestObjectsConflict -Forest $ForestName } Details = [ordered] @{Area = 'Features' Description = "When two objects are created with the same Relative Distinguished Name (RDN) in the same parent Organizational Unit or container, the conflict is recognized by the system when one of the new objects replicates to another domain controller. When this happens, one of the objects is renamed. Some sources say the RDN is mangled to make it unique. The new RDN will be <Old RDN>\0ACNF:<objectGUID>" Resolution = '' RiskLevel = 10 Resources = @('https://social.technet.microsoft.com/wiki/contents/articles/15435.active-directory-duplicate-object-name-resolution.aspx' 'http://ourwinblog.blogspot.com/2011/05/resolving-computer-object-replication.html' 'https://kickthatcomputer.wordpress.com/2014/11/22/seek-and-destroy-duplicate-ad-objects-with-cnf-in-the-name/' 'https://gallery.technet.microsoft.com/scriptcenter/Get-ADForestConflictObjects-4667fa37') } ExpectedOutput = $false } } $OptionalFeatures = [ordered] @{Enable = $true Source = [ordered] @{Name = 'Optional Features' Data = { $ADModule = Import-PrivateModule PSWinDocumentation.AD & $ADModule { Get-WinADForestOptionalFeatures -WarningAction SilentlyContinue } } Details = [ordered] @{Area = 'Features' Description = "Optional features verify availability of Recycle Bin, LAPS and PAM in the Active Directory Forest." Resolution = '' RiskLevel = 5 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{RecycleBinEnabled = @{Enable = $true Name = 'Recycle Bin Enabled' Parameters = @{Property = 'Recycle Bin Enabled' ExpectedValue = $true OperationType = 'eq' } Details = [ordered] @{Area = 'Features' Description = "The AD Recycle bin allows you to quickly restore deleted objects without the need of a system state or 3rd party backup. The recycle bin feature preserves all link valued and non link valued attributes. This means that a restored object will retain all it's settings when restored." Resolution = 'Enable AD Recycle bin for the whole forest.' RiskLevel = 5 Resources = @('https://activedirectorypro.com/enable-active-directory-recycle-bin-server-2016/') } } LapsAvailable = @{Enable = $true Name = 'LAPS Schema Extended' Parameters = @{Property = 'Laps Enabled' ExpectedValue = $true OperationType = 'eq' } Details = [ordered] @{Area = 'Features' Description = "Microsoft Local Administrator Password Solution (LAPS) is a password manager that utilizes Active Directory to manage and rotate passwords for local Administrator accounts across all of your Windows endpoints. LAPS is a great mitigation tool against lateral movement and privilege escalation, by forcing all local Administrator accounts to have unique, complex passwords, so an attacker compromising one local Administrator account can’t move laterally to other endpoints and accounts that may share that same password." Resolution = 'Enable AD Recycle bin for the whole forest.' RiskLevel = 5 Resources = @('https://blog.stealthbits.com/running-laps-in-the-race-to-security/' 'https://github.com/lithnet/laps-web' 'https://evotec.xyz/getting-bitlocker-and-laps-summary-report-with-powershell/' 'https://evotec.xyz/backing-up-bitlocker-keys-and-laps-passwords-from-active-directory/') } } PrivAccessManagement = @{Enable = $true Name = 'Privileged Access Management Enabled' Parameters = @{Property = 'Privileged Access Management Feature Enabled' ExpectedValue = $true OperationType = 'eq' } Details = [ordered] @{Area = 'Features' Description = "Privileged Access Management (PAM) is a solution that helps organizations restrict privileged access within an existing Active Directory environment." Resolution = 'Consider introducing PAM to your environment.' Severity = 'Recommendation' RiskLevel = 5 Resources = @('https://docs.microsoft.com/en-us/microsoft-identity-manager/pam/privileged-identity-management-for-active-directory-domain-services') } } } } $OrphanedAdmins = @{Enable = $true Source = @{Name = 'Orphaned Administrative Objects (AdminCount)' Data = { Get-WinADPrivilegedObjects -OrphanedOnly -Forest $ForestName } Details = [ordered] @{Area = 'Features' Description = "Consider this: a user is stamped with an AdminCount of 1, as a result of being added to Domain Admins; the user is removed from Domain Admins; the AdminCount value persists. In this instance the user is considered as orphaned. The ramifications? The AdminSDHolder ACL will be stamped upon this user every hour to protect against tampering. In turn, this can cause unexpected issues with delegation and application permissions." Resolution = '' RiskLevel = 10 Resources = @('https://blogs.technet.microsoft.com/poshchap/2016/07/29/security-focus-orphaned-admincount-eq-1-ad-users/') } ExpectedOutput = $false } } $Replication = @{Enable = $true Source = @{Name = 'Forest Replication' Data = { Get-WinADForestReplication -WarningAction SilentlyContinue -Forest $ForestName } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $null } Tests = [ordered] @{ReplicationTests = @{Enable = $true Name = 'Replication Test' Parameters = @{ExpectedValue = $true Property = 'Status' OperationType = 'eq' PropertyExtendedValue = 'StatusMessage' OverwriteName = { "Replication from $($_.Server) to $($_.ServerPartner)" } } } } } $ReplicationStatus = @{Enable = $true Source = @{Name = 'Forest Replication using RepAdmin' Data = { $Header = '"showrepl_COLUMNS","Destination DSA Site","Destination DSA","Naming Context","Source DSA Site","Source DSA","Transport Type","Number of Failures","Last Failure Time","Last Success Time","Last Failure Status"' $data = repadmin /showrepl * /csv $data[0] = $Header $data | ConvertFrom-Csv } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } Requirements = @{CommandAvailable = 'repadmin' IsInternalForest = $true } ExpectedOutput = $null } Tests = [ordered] @{ReplicationTests = @{Enable = $true Name = 'Replication Test' Parameters = @{ExpectedValue = 0 Property = 'Number of Failures' OperationType = 'eq' PropertyExtendedValue = 'Last Success Time' OverwriteName = { "Replication from $($_.'Source DSA') to $($_.'Destination DSA'), Naming Context: $($_.'Naming Context')" } } } } } $SiteLinks = @{Enable = $true Source = @{Name = 'Site Links' Data = { Get-WinADSiteLinks -Forest $ForestName } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{MinimalReplicationFrequency = @{Enable = $true Name = 'Replication Frequency should be set to maximum 60 minutes' Parameters = @{Property = 'ReplicationFrequencyInMinutes' ExpectedValue = 60 OperationType = 'lt' } } UseNotificationsForLinks = @{Enable = $true Name = 'Automatic site links should use notifications' Parameters = @{Property = 'Options' ExpectedValue = 'UseNotify' OperationType = 'contains' PropertyExtendedValue = 'Options' } } } } $SiteLinksConnections = @{Enable = $true Source = @{Name = 'Site Links Connections' Data = { Test-ADSiteLinks -Splitter ', ' -Forest $ForestName } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } 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' } } } } $Sites = @{Enable = $true Source = [ordered] @{Name = 'Sites Verification' Data = { $ADModule = Import-PrivateModule PSWinDocumentation.AD $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 ', ' } } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } 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' } } } } $TombstoneLifetime = @{Enable = $true Source = [ordered]@{Name = 'Tombstone Lifetime' Data = { $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 } } } Details = [ordered] @{Area = '' Description = '' Resolution = '' RiskLevel = 10 Resources = @() } ExpectedOutput = $true } Tests = [ordered] @{TombstoneLifetime = @{Enable = $true Name = 'TombstoneLifetime should be set to minimum of 180 days' Parameters = @{ExpectedValue = 180 Property = 'TombstoneLifeTime' OperationType = 'ge' } } } Resources = @('https://helpcenter.netwrix.com/Configure_IT_Infrastructure/AD/AD_Tombstone.html') } function ConvertTo-Source { [CmdletBinding()] param([string] $Source) if ($Source.StartsWith('Forest')) { $ProperSource = [ordered] @{Scope = 'Forest' Name = $Source -replace '^Forest' } } elseif ($Source.StartsWith('Domain')) { $ProperSource = [ordered] @{Scope = 'Domain' Name = $Source -replace '^Domain' } } elseif ($Source.StartsWith('DC')) { $ProperSource = [ordered] @{Scope = 'DomainControllers' Name = $Source -replace '^DC' } } return $ProperSource } function Get-TestimoDomainControllers { [CmdletBinding()] param([string] $Domain, [switch] $SkipRODC) try { $DC = Get-ADDomainController -Discover -DomainName $Domain $DomainControllers = Get-ADDomainController -Server $DC.HostName[0] -Filter * -ErrorAction Stop if ($SkipRODC) { $DomainControllers = $DomainControllers | Where-Object { $_.IsReadOnly -eq $false } } foreach ($_ in $DomainControllers) { if ($Script:TestimoConfiguration['Inclusions']['DomainControllers']) { if ($_ -in $Script:TestimoConfiguration['Inclusions']['DomainControllers']) { [PSCustomObject] @{Name = $($_.HostName).ToLower() IsPDC = $_.OperationMasterRoles -contains 'PDCEmulator' } } continue } if ($_.HostName -notin $Script:TestimoConfiguration['Exclusions']['DomainControllers']) { [PSCustomObject] @{Name = $($_.HostName).ToLower() IsPDC = $_.OperationMasterRoles -contains 'PDCEmulator' } } } } catch { return } } function Get-TestimoSourcesStatus { [cmdletbinding()] param([string] $Scope) $AllTests = foreach ($Source in $($Script:TestimoConfiguration.$Scope.Keys)) { $Script:TestimoConfiguration["$Scope"]["$Source"].Enable } $AllTests -contains $true } function Import-TestimoConfiguration { [CmdletBinding()] param([Object] $Configuration) if ($Configuration) { if ($Configuration -is [System.Collections.IDictionary]) { $Option = 'Hashtable' $LoadedConfiguration = $Configuration } elseif ($Configuration -is [string]) { if (Test-Path -LiteralPath $Configuration) { $Option = 'File' $FileContent = Get-Content -LiteralPath $Configuration } else { $Option = 'JSON' $FileContent = $Configuration } try { $LoadedConfiguration = $FileContent | ConvertFrom-Json } catch { Out-Informative -OverrideTitle 'Testimo' -Text "Loading configuration from JSON failed. Skipping." -Level 0 -Status $null -Domain $Domain -DomainController $DomainController -ExtendedValue ("Not JSON or syntax is incorrect.") return } } else { Out-Informative -OverrideTitle 'Testimo' -Text "Loading configuratio failed. Skipping." -Level 0 -Status $null -Domain $Domain -DomainController $DomainController -ExtendedValue ("Not JSON/Hashtable or syntax is incorrect.") } Out-Informative -OverrideTitle 'Testimo' -Text "Using configuration provided by user" -Level 0 -Start $Scopes = 'Forest', 'Domain', 'DomainControllers' foreach ($Scope in $Scopes) { if ($LoadedConfiguration -is [System.Collections.IDictionary]) { foreach ($Key in ($LoadedConfiguration.$Scope).Keys) { $Script:TestimoConfiguration[$Scope][$Key]['Enable'] = $LoadedConfiguration.$Scope.$Key.Enable if ($null -ne $LoadedConfiguration[$Scope][$Key]['Source']) { if ($null -ne $LoadedConfiguration[$Scope][$Key]['Source']['ExpectedOutput']) { $Script:TestimoConfiguration[$Scope][$Key]['Source']['ExpectedOutput'] = $LoadedConfiguration.$Scope.$Key['Source']['ExpectedOutput'] } if ($null -ne $LoadedConfiguration[$Scope][$Key]['Source']['Parameters']) { foreach ($Parameter in [string] $LoadedConfiguration[$Scope][$Key]['Source']['Parameters'].Keys) { $Script:TestimoConfiguration[$Scope][$Key]['Source']['Parameters'][$Parameter] = $LoadedConfiguration[$Scope][$Key]['Source']['Parameters'][$Parameter] } } } foreach ($Test in $LoadedConfiguration.$Scope.$Key.Tests.Keys) { $Script:TestimoConfiguration[$Scope][$Key]['Tests'][$Test]['Enable'] = $LoadedConfiguration.$Scope.$Key.Tests.$Test.Enable if ($null -ne $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.ExpectedValue) { $Script:TestimoConfiguration[$Scope][$Key]['Tests'][$Test]['Parameters']['ExpectedValue'] = $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.ExpectedValue } if ($null -ne $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.ExpectedCount) { $Script:TestimoConfiguration[$Scope][$Key]['Tests'][$Test]['Parameters']['ExpectedCount'] = $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.ExpectedCount } if ($null -ne $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.Property) { $Script:TestimoConfiguration[$Scope][$Key]['Tests'][$Test]['Parameters']['Property'] = $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.Property } if ($null -ne $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.OperationType) { $Script:TestimoConfiguration[$Scope][$Key]['Tests'][$Test]['Parameters']['OperationType'] = $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.OperationType } } } } else { foreach ($Key in ($LoadedConfiguration.$Scope).PSObject.Properties.Name) { $Script:TestimoConfiguration[$Scope][$Key]['Enable'] = $LoadedConfiguration.$Scope.$Key.Enable if ($null -ne $LoadedConfiguration.$Scope.$Key.'Source') { if ($null -ne $LoadedConfiguration.$Scope.$Key.'Source'.'ExpectedOutput') { $Script:TestimoConfiguration[$Scope][$Key]['Source']['ExpectedOutput'] = $LoadedConfiguration.$Scope.$Key.'Source'.'ExpectedOutput' } if ($null -ne $LoadedConfiguration.$Scope.$Key.'Source'.'Parameters') { foreach ($Parameter in $LoadedConfiguration.$Scope.$Key.'Source'.'Parameters'.PSObject.Properties.Name) { $Script:TestimoConfiguration[$Scope][$Key]['Source']['Parameters'][$Parameter] = $LoadedConfiguration.$Scope.$Key.'Source'.'Parameters'.$Parameter } } } foreach ($Test in $LoadedConfiguration.$Scope.$Key.Tests.PSObject.Properties.Name) { $Script:TestimoConfiguration[$Scope][$Key]['Tests'][$Test]['Enable'] = $LoadedConfiguration.$Scope.$Key.Tests.$Test.Enable if ($null -ne $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.ExpectedValue) { $Script:TestimoConfiguration[$Scope][$Key]['Tests'][$Test]['Parameters']['ExpectedValue'] = $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.ExpectedValue } if ($null -ne $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.ExpectedCount) { $Script:TestimoConfiguration[$Scope][$Key]['Tests'][$Test]['Parameters']['ExpectedCount'] = $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.ExpectedCount } if ($null -ne $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.Property) { $Script:TestimoConfiguration[$Scope][$Key]['Tests'][$Test]['Parameters']['Property'] = $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.Property } if ($null -ne $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.OperationType) { $Script:TestimoConfiguration[$Scope][$Key]['Tests'][$Test]['Parameters']['OperationType'] = $LoadedConfiguration.$Scope.$Key.Tests.$Test.Parameters.OperationType } } } } } Out-Informative -OverrideTitle 'Testimo' -Status $null -Domain $Domain -DomainController $DomainController -ExtendedValue ("Configuration loaded from $Option") -End } else { Out-Informative -OverrideTitle 'Testimo' -Text "Using configuration defaults" -Level 0 -Status $null -ExtendedValue ("No configuration provided by user") } } 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 } elseif ($Type -eq 'e') { [ConsoleColor[]] $Color = [ConsoleColor]::Red, [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 } elseif ($Type -eq 'e') { [ConsoleColor[]] $Color = [ConsoleColor]::Red, [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 } elseif ($Type -eq 'e') { [ConsoleColor[]] $Color = [ConsoleColor]::Red, [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 { [CmdletBinding()] param([string] $Text, [int] $Level, [string] $ExtendedValue = 'Input data not provided. Failing test.', [string] $Domain, [string] $DomainController, [string] $ReferenceID, [validateSet('e', 'i', 't')][string] $Type = 't') Out-Begin -Text $Text -Level $Level -Domain $Domain -DomainController $DomainController -Type $Type Out-Status -Text $Text -Status $false -ExtendedValue $ExtendedValue -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID } function Out-Informative { [CmdletBinding()] param([int] $Level = 0, [string] $OverrideTitle, [string] $Domain, [string] $DomainController, [string] $Text, [nullable[bool]] $Status, [string] $ExtendedValue, [switch] $Start, [switch] $End) if ($Start -or (-not $Start -and -not $End)) { $Type = 'i' if ($Domain -and $DomainController) { [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow $TestText = "[$Type]", "[$Domain]", "[$($DomainController)] ", $Text } elseif ($Domain) { [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow $TestText = "[$Type]", "[$Domain] ", $Text } elseif ($DomainController) { Write-Warning "Out-Begin - Shouldn't happen - Fix me." } else { [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow if ($OverrideTitle) { $TestText = "[$Type]", "[$OverrideTitle] ", $Text } else { $TestText = "[$Type]", "[Forest] ", $Text } } Write-Color -Text $TestText -Color $Color -StartSpaces $Level -NoNewLine } if ($End -or (-not $Start -and -not $End)) { 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 } } } function Out-Skip { [CmdletBinding()] param([PSCustomobject] $TestsSummary, [int] $Level = 0, [string] $Domain, [string] $DomainController, [string] $Test, [string] $Source, [string] $Reason = 'Skipping - unmet dependency') Out-Begin -Type 'i' -Text $Test -Level $Level -Domain $Domain -DomainController $DomainController Out-Status -Text $Test -Status $null -ExtendedValue $Reason -Domain $Domain -DomainController $DomainController -ReferenceID $Source $TestsSummary.Skipped = $TestsSummary.Skipped + 1 $TestsSummary.Total = $TestsSummary.Failed + $TestsSummary.Passed + $TestsSummary.Skipped $TestsSummary } function Out-Status { [CmdletBinding()] param([string] $TestID, [string] $Text, [nullable[bool]] $Status, [string] $Section, [string] $ExtendedValue, [string] $Domain, [string] $DomainController, [System.Collections.IDictionary] $SourceDetails, [System.Collections.IDictionary] $TestDetails, [string] $ReferenceID) 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" } $Output = [PSCustomObject]@{Name = $TestText Type = $TestType Status = $Status Extended = $ExtendedValue Domain = $Domain DomainController = $DomainController } if (-not $ReferenceID) { $Script:Reporting['Errors'].Add($Output) } else { if ($Domain -and $DomainController) { $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['Results'].Add($Output) } elseif ($Domain) { $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['Results'].Add($Output) } else { $Script:Reporting['Forest']['Tests'][$ReferenceID]['Results'].Add($Output) } } $Script:TestResults.Add($Output) } 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 Set-TestsStatus { [CmdletBinding()] param([string[]] $Sources, [string[]] $ExcludeSources) if ($Sources) { $Scopes = @('Forest', 'Domain', 'DomainControllers') foreach ($Scope in $Scopes) { foreach ($Test in $Script:TestimoConfiguration.$Scope.Keys) { $Script:TestimoConfiguration.$Scope[$Test]['Enable'] = $false } } foreach ($Source in $Sources) { if ($Source.StartsWith('Forest')) { $ProperSource = $Source -replace '^Forest' $Script:TestimoConfiguration['Forest'][$ProperSource]['Enable'] = $true } elseif ($Source.StartsWith('Domain')) { $ProperSource = $Source -replace '^Domain' $Script:TestimoConfiguration['Domain'][$ProperSource]['Enable'] = $true } elseif ($Source.StartsWith('DC')) { $ProperSource = $Source -replace '^DC' $Script:TestimoConfiguration['DomainControllers'][$ProperSource]['Enable'] = $true } } } foreach ($Source in $ExcludeSources) { if ($Source.StartsWith('Forest')) { $ProperSource = $Source -replace '^Forest' $Script:TestimoConfiguration['Forest'][$ProperSource]['Enable'] = $false } elseif ($Source.StartsWith('Domain')) { $ProperSource = $Source -replace '^Domain' $Script:TestimoConfiguration['Domain'][$ProperSource]['Enable'] = $false } elseif ($Source.StartsWith('DC')) { $ProperSource = $Source -replace '^DC' $Script:TestimoConfiguration['DomainControllers'][$ProperSource]['Enable'] = $false } } } function Start-TestimoEmail { [CmdletBinding()] param([string] $From, [string[]] $To, [string[]] $CC, [string[]] $BCC, [string] $Server, [int] $Port, [switch] $SSL, [string] $UserName, [string] $Password, [switch] $PasswordAsSecure, [switch] $PasswordFromFile, [string] $Priority = 'High', [string] $Subject = '[Reporting Evotec] Summary of Active Directory Tests') Email { EmailHeader { EmailFrom -Address $From EmailTo -Addresses $To EmailServer -Server $Server -UserName $UserName -Password $PasswordFromFile -PasswordAsSecure:$PasswordAsSecure -PasswordFromFile:$PasswordFromFile -Port 587 -SSL:$SSL EmailOptions -Priority $Priority -DeliveryNotifications Never EmailSubject -Subject $Subject } EmailBody -FontFamily 'Calibri' -Size 15 { EmailTable -DataTable $Results { EmailTableCondition -ComparisonType 'string' -Name 'Status' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline -Row EmailTableCondition -ComparisonType 'string' -Name 'Status' -Operator ne -Value 'True' -BackgroundColor Red -Color White -Inline -Row } -HideFooter } } -AttachSelf -Supress $false } function Start-TestimoReport { [CmdletBinding()] param([System.Collections.IDictionary] $TestResults, [string] $FilePath, [switch] $Online, [switch] $ShowHTML) if ($FilePath -eq '') { $FilePath = Get-FileName -Extension 'html' -Temporary } $ColorPassed = 'LawnGreen' $ColorSkipped = 'DeepSkyBlue' $ColorFailed = 'Tomato' $ColorPassedText = 'Black' $ColorFailedText = 'Black' $ColorSkippedText = 'Black' [Array] $PassedTests = $TestResults['Results'] | Where-Object { $_.Status -eq $true } [Array] $FailedTests = $TestResults['Results'] | Where-Object { $_.Status -eq $false } [Array] $SkippedTests = $TestResults['Results'] | Where-Object { $_.Status -ne $true -and $_.Status -ne $false } New-HTML -FilePath $FilePath -Online:$Online { New-HTMLHeader { New-HTMLSection -Invisible { New-HTMLSection { New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue } -JustifyContent flex-start -Invisible New-HTMLSection { New-HTMLText -Text $Script:Reporting['Version'] -Color Blue } -JustifyContent flex-end -Invisible } } New-HTMLTab -Name 'Summary' -IconBrands galactic-senate { New-HTMLSection -HeaderText "Tests results" -HeaderBackGroundColor DarkGray { New-HTMLPanel { New-HTMLChart { New-ChartPie -Name 'Passed' -Value ($PassedTests.Count) -Color $ColorPassed New-ChartPie -Name 'Failed' -Value ($FailedTests.Count) -Color $ColorFailed New-ChartPie -Name 'Skipped' -Value ($SkippedTests.Count) -Color $ColorSkipped } New-HTMLTable -DataTable $TestResults['Summary'] -HideFooter -DisableSearch { New-HTMLTableContent -ColumnName 'Passed' -BackGroundColor $ColorPassed -Color $ColorPassedText New-HTMLTableContent -ColumnName 'Failed' -BackGroundColor $ColorFailed -Color $ColorFailedText New-HTMLTableContent -ColumnName 'Skipped' -BackGroundColor $ColorSkipped -Color $ColorSkippedText } } New-HTMLPanel { New-HTMLTable -DataTable $TestResults['Results'] { New-HTMLTableCondition -Name 'Status' -Value $true -BackgroundColor $ColorPassed -Color $ColorPassedText New-HTMLTableCondition -Name 'Status' -Value $false -BackgroundColor $ColorFailed -Color $ColorFailedText New-HTMLTableCondition -Name 'Status' -Value $null -BackgroundColor $ColorSkipped -Color $ColorSkippedText } } } } if ($TestResults['Forest']['Tests'].Count -gt 0) { New-HTMLTab -Name 'Forest' -IconBrands first-order { foreach ($Source in $TestResults['Forest']['Tests'].Keys) { $Name = $TestResults['Forest']['Tests'][$Source]['Name'] $Data = $TestResults['Forest']['Tests'][$Source]['Data'] $SourceCode = $TestResults['Forest']['Tests'][$Source]['SourceCode'] $Results = $TestResults['Forest']['Tests'][$Source]['Results'] [Array] $PassedTestsSingular = $TestResults['Forest']['Tests'][$Source]['Results'] | Where-Object { $_.Status -eq $true } [Array] $FailedTestsSingular = $TestResults['Forest']['Tests'][$Source]['Results'] | Where-Object { $_.Status -eq $false } [Array] $SkippedTestsSingular = $TestResults['Forest']['Tests'][$Source]['Results'] | Where-Object { $_.Status -ne $true -and $_.Status -ne $false } New-HTMLSection -HeaderText $Name -HeaderBackGroundColor DarkGray { New-HTMLContainer { New-HTMLPanel { New-HTMLChart { New-ChartPie -Name 'Passed' -Value ($PassedTestsSingular.Count) -Color $ColorPassed New-ChartPie -Name 'Failed' -Value ($FailedTestsSingular.Count) -Color $ColorFailed New-ChartPie -Name 'Skipped' -Value ($SkippedTestsSingular.Count) -Color $ColorSkipped } New-HTMLCodeBlock -Code $SourceCode -Style 'PowerShell' -Theme enlighter } } New-HTMLContainer { New-HTMLPanel { New-HTMLTable -DataTable $Data New-HTMLTable -DataTable $Results { New-HTMLTableCondition -Name 'Status' -Value $true -BackgroundColor $ColorPassed -Color $ColorPassedText New-HTMLTableCondition -Name 'Status' -Value $false -BackgroundColor $ColorFailed -Color $ColorFailedText New-HTMLTableCondition -Name 'Status' -Value $null -BackgroundColor $ColorSkipped -Color $ColorSkippedText } } } } } } } foreach ($Domain in $TestResults['Domains'].Keys) { if ($TestResults['Domains'][$Domain]['Tests'].Count -gt 0 -or $TestResults['Domains'][$Domain]['DomainControllers'].Count -gt 0) { New-HTMLTab -Name "Domain $Domain" -IconBrands deskpro { foreach ($Source in $TestResults['Domains'][$Domain]['Tests'].Keys) { $Name = $TestResults['Domains'][$Domain]['Tests'][$Source]['Name'] $Data = $TestResults['Domains'][$Domain]['Tests'][$Source]['Data'] $SourceCode = $TestResults['Domains'][$Domain]['Tests'][$Source]['SourceCode'] $Results = $TestResults['Domains'][$Domain]['Tests'][$Source]['Results'] [Array] $PassedTestsSingular = $TestResults['Domains'][$Domain]['Tests'][$Source]['Results'] | Where-Object { $_.Status -eq $true } [Array] $FailedTestsSingular = $TestResults['Domains'][$Domain]['Tests'][$Source]['Results'] | Where-Object { $_.Status -eq $false } [Array] $SkippedTestsSingular = $TestResults['Domains'][$Domain]['Tests'][$Source]['Results'] | Where-Object { $_.Status -ne $true -and $_.Status -ne $false } New-HTMLSection -HeaderText $Name -HeaderBackGroundColor DarkGray { New-HTMLContainer { New-HTMLPanel { New-HTMLChart { New-ChartPie -Name 'Passed' -Value ($PassedTestsSingular.Count) -Color $ColorPassed New-ChartPie -Name 'Failed' -Value ($FailedTestsSingular.Count) -Color $ColorFailed New-ChartPie -Name 'Skipped' -Value ($SkippedTestsSingular.Count) -Color $ColorSkipped } New-HTMLCodeBlock -Code $SourceCode -Style 'PowerShell' -Theme enlighter } } New-HTMLContainer { New-HTMLPanel { New-HTMLTable -DataTable $Data New-HTMLTable -DataTable $Results { New-HTMLTableCondition -Name 'Status' -Value $true -BackgroundColor $ColorPassed -Color $ColorPassedText New-HTMLTableCondition -Name 'Status' -Value $false -BackgroundColor $ColorFailed -Color $ColorFailedText New-HTMLTableCondition -Name 'Status' -Value $null -BackgroundColor $ColorSkipped -Color $ColorSkippedText } } } } } if ($TestResults['Domains'][$Domain]['DomainControllers'].Count -gt 0) { foreach ($DC in $TestResults['Domains'][$Domain]['DomainControllers'].Keys) { New-HTMLSection -HeaderText "Domain Controller - $DC" -HeaderBackGroundColor DarkSlateGray { New-HTMLContainer { foreach ($Source in $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'].Keys) { $Name = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Name'] $Data = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Data'] $SourceCode = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['SourceCode'] $Results = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Results'] [Array] $PassedTestsSingular = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Results'] | Where-Object { $_.Status -eq $true } [Array] $FailedTestsSingular = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Results'] | Where-Object { $_.Status -eq $false } [Array] $SkippedTestsSingular = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Results'] | Where-Object { $_.Status -ne $true -and $_.Status -ne $false } New-HTMLSection -HeaderText $Name -HeaderBackGroundColor DarkGray { New-HTMLContainer { New-HTMLPanel { New-HTMLChart { New-ChartPie -Name 'Passed' -Value ($PassedTestsSingular.Count) -Color $ColorPassed New-ChartPie -Name 'Failed' -Value ($FailedTestsSingular.Count) -Color $ColorFailed New-ChartPie -Name 'Skipped' -Value ($SkippedTestsSingular.Count) -Color $ColorSkipped } New-HTMLCodeBlock -Code $SourceCode -Style 'PowerShell' -Theme enlighter } } New-HTMLContainer { New-HTMLPanel { New-HTMLTable -DataTable $Data New-HTMLTable -DataTable $Results { New-HTMLTableCondition -Name 'Status' -Value $true -BackgroundColor $ColorPassed -Color $ColorPassedText New-HTMLTableCondition -Name 'Status' -Value $false -BackgroundColor $ColorFailed -Color $ColorFailedText New-HTMLTableCondition -Name 'Status' -Value $null -BackgroundColor $ColorSkipped -Color $ColorSkippedText } } } } } } } } } } } } } -ShowHTML:$ShowHTML } 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-Informative -Text $SummaryText -Status $null -ExtendedValue '' -Domain $Domain -DomainController $DomainController -Level ($LevelSummary - 3) $TestsSummaryTogether = @(foreach ($Source in $($Script:TestimoConfiguration.$Scope.Keys)) { $CurrentSection = $Script:TestimoConfiguration.$Scope[$Source] if ($null -eq $CurrentSection) { Write-Warning "Source $Source in scope: $Scope is defined improperly. Please verify." continue } if ($CurrentSection['Enable'] -eq $true) { $Time = Start-TimeLog $CurrentSource = $CurrentSection['Source'] $CurrentTests = $CurrentSection['Tests'] [Array] $AllTests = $CurrentSection['Tests'].Keys $ReferenceID = $Source $TestsSummary = [PSCustomobject] @{Passed = 0 Failed = 0 Skipped = 0 Total = 0 } if ($Domain -and $DomainController) { $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID] = [ordered] @{Name = $CurrentSource['Name'] SourceCode = $CurrentSource['Data'] Details = $CurrentSource['Details'] Results = [System.Collections.Generic.List[PSCustomObject]]::new() Domain = $Domain DomainController = $DomainController } } elseif ($Domain) { $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID] = [ordered] @{Name = $CurrentSource['Name'] SourceCode = $CurrentSource['Data'] Details = $CurrentSource['Details'] Results = [System.Collections.Generic.List[PSCustomObject]]::new() Domain = $Domain DomainController = $DomainController } } else { $Script:Reporting['Forest']['Tests'][$ReferenceID] = [ordered] @{Name = $CurrentSource['Name'] SourceCode = $CurrentSource['Data'] Details = $CurrentSource['Details'] Results = [System.Collections.Generic.List[PSCustomObject]]::new() Domain = $Domain DomainController = $DomainController } } if (-not $CurrentSection['Source']) { Write-Warning "Source $Source in scope: $Scope is defined improperly. Please verify." continue } if ($CurrentSource['Requirements']) { if ($null -ne $CurrentSource['Requirements']['IsDomainRoot']) { if (-not $CurrentSource['Requirements']['IsDomainRoot'] -eq $IsDomainRoot) { Out-Skip -Test $CurrentSource['Name'] -DomainController $DomainController -Domain $Domain -TestsSummary $TestsSummary -Source $ReferenceID -Level $Level continue } } if ($null -ne $CurrentSource['Requirements']['IsPDC']) { if (-not $CurrentSource['Requirements']['IsPDC'] -eq $IsPDC) { Out-Skip -Test $CurrentSource['Name'] -DomainController $DomainController -Domain $Domain -TestsSummary $TestsSummary -Source $ReferenceID -Level $Level continue } } if ($null -ne $CurrentSource['Requirements']['OperatingSystem']) {} if ($null -ne $CurrentSource['Requirements']['CommandAvailable']) { [Array] $Commands = foreach ($Command in $CurrentSource['Requirements']['CommandAvailable']) { $OutputCommand = Get-Command -Name $Command -ErrorAction SilentlyContinue if (-not $OutputCommand) { $false } } if ($Commands -contains $false) { $CommandsTested = $CurrentSource['Requirements']['CommandAvailable'] -join ', ' Out-Skip -Test $CurrentSource['Name'] -DomainController $DomainController -Domain $Domain -TestsSummary $TestsSummary -Source $ReferenceID -Level $Level -Reason "Skipping - At least one command unavailable ($CommandsTested)" continue } } if ($null -ne $CurrentSource['Requirements']['IsInternalForest']) { if ($CurrentSource['Requirements']['IsInternalForest'] -eq $true) { if ($ForestName) { Out-Skip -Test $CurrentSource['Name'] -DomainController $DomainController -Domain $Domain -TestsSummary $TestsSummary -Source $ReferenceID -Level $Level -Reason "Skipping - External forest requested. Not supported test." 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 ($Domain -and $DomainController) { $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['Data'] = $Object $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['DetailsTests'] = [ordered]@{} $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['ResultsTests'] = [ordered]@{} } elseif ($Domain) { $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['Data'] = $Object $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['DetailsTests'] = [ordered]@{} $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['ResultsTests'] = [ordered]@{} } else { $Script:Reporting['Forest']['Tests'][$ReferenceID]['Data'] = $Object $Script:Reporting['Forest']['Tests'][$ReferenceID]['DetailsTests'] = [ordered]@{} $Script:Reporting['Forest']['Tests'][$ReferenceID]['ResultsTests'] = [ordered]@{} } if ($Object -and $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 -ReferenceID $ReferenceID $TestsSummary.Passed = $TestsSummary.Passed + 1 } elseif ($Object -and $CurrentSource['ExpectedOutput'] -eq $false) { $FailAllTests = $false Out-Failure -Text $CurrentSource['Name'] -Level $LevelTest -ExtendedValue 'Data is available. This is a bad thing.' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID $TestsSummary.Failed = $TestsSummary.Failed + 1 } elseif ($Object -and $null -eq $CurrentSource['ExpectedOutput']) { $FailAllTests = $false Out-Begin -Text $CurrentSource['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController Out-Status -Text $CurrentSource['Name'] -Status $null -ExtendedValue 'Data is available.' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID $TestsSummary.Skipped = $TestsSummary.Skipped + 1 } elseif ($null -eq $Object -and $CurrentSource['ExpectedOutput'] -eq $true) { $FailAllTests = $true Out-Failure -Text $CurrentSource['Name'] -Level $LevelTest -ExtendedValue 'No data available.' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID $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 -ReferenceID $ReferenceID $TestsSummary.Passed = $TestsSummary.Passed + 1 } elseif ($null -eq $Object -and $null -eq $CurrentSource['ExpectedOutput']) { $FailAllTests = $false Out-Begin -Text $CurrentSource['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController Out-Status -Text $CurrentSource['Name'] -Status $null -ExtendedValue 'No data returned.' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID $TestsSummary.Skipped = $TestsSummary.Skipped + 1 } else { $FailAllTests = $true Out-Failure -Text $CurrentSource['Name'] -Level $LevelTest -ExtendedValue 'No data available.' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID $TestsSummary.Failed = $TestsSummary.Failed + 1 } foreach ($Test in $AllTests) { if ($Domain -and $DomainController) { $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['DetailsTests'][$Test] = $CurrentSection['Tests'][$Test]['Details'] } elseif ($Domain) { $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['DetailsTests'][$Test] = $CurrentSection['Tests'][$Test]['Details'] } else { $Script:Reporting['Forest']['Tests'][$ReferenceID]['DetailsTests'][$Test] = $CurrentSection['Tests'][$Test]['Details'] } $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 -ReferenceID $ReferenceID { Test-StepOne -Object $Object -Domain $Domain -DomainController $DomainController @Parameters -Level $LevelTest -TestName $CurrentTest['Name'] -ReferenceID $ReferenceID -Requirements $CurrentTest['Requirements'] } $TestsSummary.Passed = $TestsSummary.Passed + ($TestsResults | Where-Object { $_ -eq $true }).Count $TestsSummary.Failed = $TestsSummary.Failed + ($TestsResults | Where-Object { $_ -eq $false }).Count } else { $TestsResults = $null $TestsSummary.Failed = $TestsSummary.Failed + 1 Out-Failure -Text $CurrentTest['Name'] -Level $LevelTestFailure -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID } if ($Domain -and $DomainController) { $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['ResultsTests'][$Test] = $TestsResults } elseif ($Domain) { $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['ResultsTests'][$Test] = $TestsResults } else { $Script:Reporting['Forest']['Tests'][$ReferenceID]['ResultsTests'][$Test] = $TestsResults } } 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 if ($Domain -and $DomainController) { $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Summary'] = $TestsSummaryFinal } elseif ($Domain) { $Script:Reporting['Domains'][$Domain]['Summary'] = $TestsSummaryFinal } else { $Script:Reporting['Summary'] = $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, [string] $ReferenceID) 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 -ReferenceID $ReferenceID } } } } function Start-TestProcessing { [CmdletBinding()] param([ScriptBlock] $Execute, [string] $Test, [switch] $OutputRequired, [nullable[bool]] $ExpectedStatus, [int] $Level = 0, [switch] $Simple, [string] $Domain, [string] $DomainController, [string] $ReferenceID) if ($Execute) { Out-Informative -Text $Test -Level $Level -Domain $Domain -DomainController $DomainController -Start 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-Informative -Text $Test -Status $TestResult -ExtendedValue $O.Extended -Domain $Domain -DomainController $DomainController -End } else { Out-Informative -Text $Test -Status $TestResult -ExtendedValue $ErrorMessage -Domain $Domain -DomainController $DomainController -End } } } function Test-StepOne { [CmdletBinding()] param([string] $Domain, [string] $DomainController, [Array] $Object, [string] $TestName, [string] $OperationType, [int] $Level, [string[]] $Property, [string[]] $PropertyExtendedValue, [Object] $ExpectedValue, [nullable[int]] $ExpectedCount, [string] $OperationResult, [string] $ReferenceID, [nullable[bool]] $ExpectedResult, [nullable[bool]] $ExpectedOutput, [scriptblock] $WhereObject, [scriptblock] $OverwriteName, [System.Collections.IDictionary] $Requirements) if ($OperationType -eq '') { $OperationType = 'eq' } if ($Object) { if ($WhereObject) { $Object = $Object | Where-Object $WhereObject } if ($null -ne $Requirements) { if ($null -ne $Requirements['ExpectedOutput']) {} } if ($null -eq $Object) { if ($ExpectedResult -eq $false) { Out-Begin -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController Out-Status -Text $TestName -Status $true -ExtendedValue "Data is not available. This is expected." -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID return $true } elseif ($ExpectedResult -eq $true) { Out-Begin -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController Out-Status -Text $TestName -Status $false -ExtendedValue 'Data is not available. This is not expected.' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID return $false } if ($null -eq $ExpectedOutput -or $ExpectedOutput -eq $true) { Out-Begin -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController Out-Status -Text $TestName -Status $false -ExtendedValue 'Data is not available.' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID return $false } else { Out-Begin -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController Out-Status -Text $TestName -Status $true -ExtendedValue "Data is not available, but it's not required." -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID return $true } } else { if ($ExpectedResult -eq $false) { Out-Begin -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController Out-Status -Text $TestName -Status $false -ExtendedValue 'Data is available. This is not expected.' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID return $false } elseif ($ExpectedResult -eq $true) { Out-Begin -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController Out-Status -Text $TestName -Status $true -ExtendedValue "Data is available. This is expected." -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID return $true } } if ($null -ne $ExpectedCount) { if ($OverwriteName) { $TestName = & $OverwriteName } Test-StepTwo -Object $Object -ExpectedCount $ExpectedCount -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue -OperationResult $OperationResult -ReferenceID $ReferenceID -ExpectedOutput $ExpectedOutput } else { foreach ($_ in $Object) { if ($OverwriteName) { $TestName = & $OverwriteName } Test-StepTwo -Object $_ -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue -OperationResult $OperationResult -ReferenceID $ReferenceID -ExpectedOutput $ExpectedOutput } } } } function Test-StepTwo { [CmdletBinding()] param([string] $Domain, [string] $DomainController, [Array] $Object, [string] $TestName, [string] $OperationType, [int] $Level, [string[]] $Property, [string[]] $PropertyExtendedValue, [Array] $ExpectedValue, [nullable[int]] $ExpectedCount, [string] $OperationResult, [string] $ReferenceID, [nullable[bool]] $ExpectedOutput) Out-Begin -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController $ScriptBlock = { $Operators = @{'lt' = 'Less Than' 'gt' = 'Greater Than' 'le' = 'Less Or Equal' 'ge' = 'Greater Or Equal' 'eq' = 'Equal' 'contains' = 'Contains' 'notcontains' = 'Not contains' 'like' = 'Like' 'match' = 'Match' 'notmatch' = 'Not match' 'notin' = 'Not in' 'in' = 'Either Value' } [Object] $TestedValue = $Object foreach ($V in $Property) { $TestedValue = $TestedValue.$V } if ($null -ne $ExpectedCount) { if ($OperationType -eq 'lt') { $TestResult = $TestedValue.Count -lt $ExpectedCount } elseif ($OperationType -eq 'gt') { $TestResult = $TestedValue.Count -gt $ExpectedCount } elseif ($OperationType -eq 'ge') { $TestResult = $TestedValue.Count -ge $ExpectedCount } elseif ($OperationType -eq 'le') { $TestResult = $TestedValue.Count -le $ExpectedCount } elseif ($OperationType -eq 'like') { $TestResult = $TestedValue.Count -like $ExpectedCount } elseif ($OperationType -eq 'contains') { $TestResult = $TestedValue.Count -contains $ExpectedCount } elseif ($OperationType -eq 'in') { $TestResult = $ExpectedCount -in $TestedValue.Count } elseif ($OperationType -eq 'notin') { $TestResult = $ExpectedCount -notin $TestedValue.Count } else { $TestResult = $TestedValue.Count -eq $ExpectedCount } $TextTestedValue = $TestedValue.Count $TextExpectedValue = $ExpectedCount } elseif ($null -ne $ExpectedValue) { $OutputValues = [System.Collections.Generic.List[Object]]::new() if ($null -eq $TestedValue -and $null -ne $ExpectedValue) { $TestResult = for ($i = 0; $i -lt $ExpectedValue.Count; $i++) { $false if ($ExpectedValue[$i] -is [string] -and $ExpectedValue[$i] -like '*Get-Date*') { [scriptblock] $DateConversion = [scriptblock]::Create($ExpectedValue[$i]) $CompareValue = & $DateConversion } else { $CompareValue = $ExpectedValue[$I] } $OutputValues.Add($CompareValue) } $TextExpectedValue = $OutputValues -join ', ' $TextTestedValue = 'Null' } else { [Array] $TestResult = @(if ($OperationType -eq 'notin') { $ExpectedValue -notin $TestedValue $TextExpectedValue = $ExpectedValue } elseif ($OperationType -eq 'in') { $TestedValue -in $ExpectedValue $TextExpectedValue = $ExpectedValue -join ' or ' } else { for ($i = 0; $i -lt $ExpectedValue.Count; $i++) { if ($ExpectedValue[$i] -is [string] -and $ExpectedValue[$i] -like '*Get-Date*') { [scriptblock] $DateConversion = [scriptblock]::Create($ExpectedValue[$i]) $CompareValue = & $DateConversion } else { $CompareValue = $ExpectedValue[$I] } if ($OperationType -eq 'lt') { $TestedValue -lt $CompareValue } elseif ($OperationType -eq 'gt') { $TestedValue -gt $CompareValue } elseif ($OperationType -eq 'ge') { $TestedValue -ge $CompareValue } elseif ($OperationType -eq 'le') { $TestedValue -le $CompareValue } elseif ($OperationType -eq 'like') { $TestedValue -like $CompareValue } elseif ($OperationType -eq 'contains') { $TestedValue -contains $CompareValue } elseif ($OperationType -eq 'notcontains') { $TestedValue -notcontains $CompareValue } elseif ($OperationType -eq 'match') { $TestedValue -match $CompareValue } elseif ($OperationType -eq 'notmatch') { $TestedValue -notmatch $CompareValue } else { $TestedValue -eq $CompareValue } $OutputValues.Add($CompareValue) } $TextExpectedValue = $OutputValues -join ', ' } $TextTestedValue = $TestedValue) } } else { if ($ExpectedOutput -eq $false) { [Array] $TestResult = @(if ($null -eq $TestedValue) { $true } else { $false }) $TextExpectedValue = 'No output' } 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])): $($TextExpectedValue)" } else { $ReportResult = $false $ReportExtended = "Expected value ($($Operators[$OperationType])): $TextExpectedValue, Found value: $($TextTestedValue)" } } else { if ($TestResult -notcontains $false) { $ReportResult = $true $ReportExtended = "Expected value ($($Operators[$OperationType])): $($TextExpectedValue)" } else { $ReportResult = $false $ReportExtended = "Expected value ($($Operators[$OperationType])): $TextExpectedValue, Found value: $($TextTestedValue)" } } } if ($PropertyExtendedValue.Count -gt 0) { $ReportExtended = $Object foreach ($V in $PropertyExtendedValue) { $ReportExtended = $ReportExtended.$V } $ReportExtended = $ReportExtended -join ', ' } Out-Status -Text $TestName -Status $ReportResult -ExtendedValue $ReportExtended -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID 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 -ReferenceID $ReferenceID return $False } } } $Script:TestimoConfiguration = [ordered] @{Exclusions = [ordered] @{Domains = @() DomainControllers = @() } Inclusions = [ordered] @{Domains = @() DomainControllers = @() } Forest = [ordered]@{Backup = $Backup Replication = $Replication ReplicationStatus = $ReplicationStatus OptionalFeatures = $OptionalFeatures Sites = $Sites SiteLinks = $SiteLinks SiteLinksConnections = $SiteLinksConnections Roles = $ForestFSMORoles OrphanedAdmins = $OrphanedAdmins ObjectsWithConflict = $ObjectsWithConflict TombstoneLifetime = $TombstoneLifetime } Domain = [ordered] @{Roles = $DomainFSMORoles WellKnownFolders = $WellKnownFolders PasswordComplexity = $PasswordComplexity GroupPolicyMissingPermissions = $GroupPolicyMissingPermissions GroupPolicyPermissionConsistency = $GroupPolicyPermissionConsistency GroupPolicyOwner = $GroupPolicyOwner GroupPolicyPermissionUnknown = $GroupPolicyPermissionUnknown GroupPolicyADM = $GroupPolicyADM GroupPolicySysvol = $GroupPolicySysvol Trusts = $Trusts OrphanedForeignSecurityPrincipals = $OrphanedForeignSecurityPrincipals OrganizationalUnitsEmpty = $OrganizationalUnitsEmpty OrganizationalUnitsProtected = $OrganizationalUnitsProtected DNSScavengingForPrimaryDNSServer = $DNSScavengingForPrimaryDNSServer DNSForwaders = $DNSForwaders DnsZonesAging = $DnsZonesAging KerberosAccountAge = $KerberosAccountAge SecurityGroupsAccountOperators = $SecurityGroupsAccountOperators SecurityGroupsSchemaAdmins = $SecurityGroupsSchemaAdmins SecurityUsers = $SecurityUsers SecurityUsersAcccountAdministrator = $SecurityUsersAcccountAdministrator SecurityKRBGT = $SecurityKRBGT SysVolDFSR = $SysVolDFSR 'DNSZonesForest0ADEL' = $DNSZonesForest0ADEL 'DNSZonesDomain0ADEL' = $DNSZonesDomain0ADEL DHCPAuthorized = $DHCPAuthorized ComputersUnsupported = $ComputersUnsupported ComputersUnsupportedMainstream = $ComputersUnsupportedMainstream ExchangeUsers = $ExchangeUsers DuplicateObjects = $DuplicateObjects } DomainControllers = [ordered] @{Information = $Information WindowsRemoteManagement = $WindowsRemoteManagement EventLogs = $EventLogs OperatingSystem = $OperatingSystem Services = $Services LDAP = $LDAP LDAPInsecureBindings = $LDAPInsecureBindings Pingable = $Pingable Ports = $Ports RDPPorts = $RDPPorts RDPSecurity = $RDPSecurity DiskSpace = $DiskSpace TimeSettings = $TimeSettings TimeSynchronizationInternal = $TimeSynchronizationInternal TimeSynchronizationExternal = $TimeSynchronizationExternal NetworkCardSettings = $NetworkCardSettings WindowsUpdates = $WindowsUpdates WindowsRolesAndFeatures = $WindowsRolesAndFeatures DnsResolveInternal = $DNSResolveInternal DnsResolveExternal = $DNSResolveExternal DnsNameServes = $DNSNameServers SMBProtocols = $SMBProtocols SMBShares = $SMBShares SMBSharesPermissions = $SMBSharesPermissions DFS = $DFS NTDSParameters = $NTDSParameters GroupPolicySYSVOLDC = $GroupPolicySYSVOLDC LanManagerSettings = $LanManagerSettings Diagnostics = $Diagnostics LanManServer = $LanManServer MSSLegacy = $MSSLegacy FileSystem = $FileSystem NetSessionEnumeration = $NetSessionEnumeration ServiceWINRM = $ServiceWINRM UNCHardenedPaths = $UNCHardenedPaths DNSForwaders = $DCDNSForwaders } Debug = [ordered] @{ShowErrors = $false } } function Get-TestimoConfiguration { [CmdletBinding()] param([switch] $AsJson, [string] $FilePath) $NewConfig = [ordered] @{} $Scopes = 'Forest', 'Domain', 'DomainControllers' foreach ($Scope in $Scopes) { $NewConfig[$Scope] = [ordered] @{} foreach ($Source in ($Script:TestimoConfiguration[$Scope]).Keys) { $NewConfig[$Scope][$Source] = [ordered] @{} $NewConfig[$Scope][$Source]['Enable'] = $Script:TestimoConfiguration[$Scope][$Source]['Enable'] if ($null -ne $Script:TestimoConfiguration[$Scope][$Source]['Source']['ExpectedOutput']) { $NewConfig[$Scope][$Source]['Source'] = [ordered] @{} $NewConfig[$Scope][$Source]['Source']['ExpectedOutput'] = $Script:TestimoConfiguration[$Scope][$Source]['Source']['ExpectedOutput'] } if ($null -ne $Script:TestimoConfiguration[$Scope][$Source]['Source']['Parameters']) { $NewConfig[$Scope][$Source]['Source'] = [ordered] @{} $NewConfig[$Scope][$Source]['Source']['Parameters'] = $Script:TestimoConfiguration[$Scope][$Source]['Source']['Parameters'] } $NewConfig[$Scope][$Source]['Tests'] = [ordered] @{} foreach ($Test in $Script:TestimoConfiguration[$Scope][$Source]['Tests'].Keys) { $NewConfig[$Scope][$Source]['Tests'][$Test] = [ordered] @{} $NewConfig[$Scope][$Source]['Tests'][$Test]['Enable'] = $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Enable'] $NewConfig[$Scope][$Source]['Tests'][$Test]['Parameters'] = [ordered] @{} if ($null -ne $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Parameters']) { if ($null -ne $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Parameters']['Property']) { if ($null -ne $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Parameters']['Property']) { $NewConfig[$Scope][$Source]['Tests'][$Test]['Parameters']['Property'] = $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Parameters']['Property'] } if ($null -ne $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Parameters']['ExpectedValue']) { $NewConfig[$Scope][$Source]['Tests'][$Test]['Parameters']['ExpectedValue'] = $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Parameters']['ExpectedValue'] } if ($null -ne $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Parameters']['ExpectedCount']) { $NewConfig[$Scope][$Source]['Tests'][$Test]['Parameters']['ExpectedCount'] = $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Parameters']['ExpectedCount'] } if ($null -ne $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Parameters']['OperationType']) { $NewConfig[$Scope][$Source]['Tests'][$Test]['Parameters']['OperationType'] = $Script:TestimoConfiguration[$Scope][$Source]['Tests'][$Test]['Parameters']['OperationType'] } } } } } } if ($FilePath) { $NewConfig | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $FilePath return } if ($AsJSON) { return $NewConfig | ConvertTo-Json -Depth 10 } return $NewConfig } function Get-TestimoSources { [CmdletBinding()] param([string[]] $Sources, [switch] $SourcesOnly, [switch] $Enabled, [switch] $Advanced) if (-not $Sources) { $Sources = @($ForestKeys = $Script:TestimoConfiguration.Forest.Keys $DomainKeys = $Script:TestimoConfiguration.Domain.Keys $DomainControllerKeys = $Script:TestimoConfiguration.DomainControllers.Keys $TestSources = @(foreach ($Key in $ForestKeys) { if ($Enabled) { if ($Script:TestimoConfiguration.Forest["$Key"].Enable) { "Forest$Key" } } else { "Forest$Key" } } foreach ($Key in $DomainKeys) { if ($Enabled) { if ($Script:TestimoConfiguration.Domain["$Key"].Enable) { "Domain$Key" } } else { "Domain$Key" } } foreach ($Key in $DomainControllerKeys) { if ($Enabled) { if ($Script:TestimoConfiguration.DomainControllers["$Key"].Enable) { "DC$Key" } } else { "DC$Key" } }) $TestSources | Sort-Object) } if ($SourcesOnly) { return $TestSources } foreach ($S in $Sources) { $DetectedSource = ConvertTo-Source -Source $S $Scope = $DetectedSource.Scope $Name = $DetectedSource.Name $Object = [ordered]@{Source = $S Scope = $Scope Name = $Name Tests = $Script:TestimoConfiguration.$Scope[$Name].Tests.Keys } $Object['Area'] = $Script:TestimoConfiguration.$Scope[$Name].Source.Details.Area $Object['Category'] = $Script:TestimoConfiguration.$Scope[$Name].Source.Details.Category $Object['Severity'] = $Script:TestimoConfiguration.$Scope[$Name].Source.Details.Severity $Object['RiskLevel'] = $Script:TestimoConfiguration.$Scope[$Name].Source.Details.RiskLevel $Object['Description'] = $Script:TestimoConfiguration.$Scope[$Name].Source.Details.Description $Object['Resolution'] = $Script:TestimoConfiguration.$Scope[$Name].Source.Details.Resolution $Object['Resources'] = $Script:TestimoConfiguration.$Scope[$Name].Source.Details.Resources if ($Advanced) { $Object['Advanced'] = $Script:TestimoConfiguration.$Scope[$Name] } [PSCustomObject] $Object } } function Import-PrivateModule { [cmdletBinding()] param([string] $Name, [switch] $Portable) try { $ADModule = Import-Module -PassThru -Name $Name -ErrorAction Stop } catch { if ($_.Exception.Message -like '*was not loaded because no valid module file was found in any module directory*') { $Module = Get-Module -Name $Name if ($Module) { $ADModule = Import-Module $Module -PassThru } } } $ADModule } function Invoke-Testimo { [alias('Test-ImoAD', 'Test-IMO')] [CmdletBinding()] param([ValidateScript( { $_ -in (& $SourcesAutoCompleter) })] [string[]] $Sources, [ValidateScript( { $_ -in (& $SourcesAutoCompleter) })] [string[]] $ExcludeSources, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [string[]] $IncludeDomains, [string[]] $IncludeDomainControllers, [string] $ForestName, [switch] $ReturnResults, [switch] $ShowErrors, [switch] $ExtendedResults, [Object] $Configuration, [string] $ReportPath, [switch] $ShowReport, [switch] $SkipRODC, [switch] $Online) if (-not $Script:DefaultSources) { $Script:DefaultSources = Get-TestimoSources -Enabled -SourcesOnly } else { Set-TestsStatus -Sources $Script:DefaultSources } $Script:Reporting = [ordered] @{} $Script:Reporting['Version'] = '' $Script:Reporting['Errors'] = [System.Collections.Generic.List[PSCustomObject]]::new() $Script:Reporting['Results'] = $null $Script:Reporting['Summary'] = [ordered] @{} $Script:Reporting['Forest'] = [ordered] @{} $Script:Reporting['Forest']['Summary'] = $null $Script:Reporting['Forest']['Tests'] = [ordered] @{} $Script:Reporting['Domains'] = [ordered] @{} $TestimoVersion = Get-Command -Name 'Invoke-Testimo' -ErrorAction SilentlyContinue [Array] $GitHubReleases = (Get-GitHubLatestRelease -Url "https://api.github.com/repos/evotecit/Testimo/releases") $LatestVersion = $GitHubReleases[0] if (-not $LatestVersion.Errors) { if ($TestimoVersion.Version -eq $LatestVersion.Version) { $Script:Reporting['Version'] = "Current/Latest: $($LatestVersion.Version) at $($LatestVersion.PublishDate)" } elseif ($TestimoVersion.Version -lt $LatestVersion.Version) { $Script:Reporting['Version'] = "Current: $($TestimoVersion.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Update?" } elseif ($TestimoVersion.Version -gt $LatestVersion.Version) { $Script:Reporting['Version'] = "Current: $($TestimoVersion.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Lucky you!" } } else { $Script:Reporting['Version'] = "Current: $($TestimoVersion.Version)" } Out-Informative -OverrideTitle 'Testimo' -Text 'Version' -Level 0 -Status $null -ExtendedValue $Script:Reporting['Version'] Import-TestimoConfiguration -Configuration $Configuration $global:ProgressPreference = 'SilentlyContinue' $global:ErrorActionPreference = 'Stop' $Script:TestResults = [System.Collections.Generic.List[PSCustomObject]]::new() $Script:TestimoConfiguration.Debug.ShowErrors = $ShowErrors $Script:TestimoConfiguration.Exclusions.Domains = $ExcludeDomains $Script:TestimoConfiguration.Exclusions.DomainControllers = $ExcludeDomainControllers $Script:TestimoConfiguration.Inclusions.Domains = $IncludeDomains $Script:TestimoConfiguration.Inclusions.DomainControllers = $IncludeDomainControllers Set-TestsStatus -Sources $Sources -ExcludeSources $ExcludeSources if ($Script:TestimoConfiguration.Inclusions.Domains) { Out-Informative -Text 'Only following Domains will be scanned' -Level 0 -Status $null -ExtendedValue ($Script:TestimoConfiguration.Inclusions.Domains -join ', ') } if ($Script:TestimoConfiguration.Inclusions.DomainControllers) { Out-Informative -Text 'Only following Domain Controllers will be scanned' -Level 0 -Status $null -ExtendedValue ($Script:TestimoConfiguration.Inclusions.DomainControllers -join ', ') } if ($Script:TestimoConfiguration.Exclusions.Domains -and -not $Script:TestimoConfiguration.Inclusions.Domains) { Out-Informative -Text 'Following Domains will be ignored' -Level 0 -Status $null -ExtendedValue ($Script:TestimoConfiguration.Exclusions.Domains -join ', ') } if ($Script:TestimoConfiguration.Exclusions.DomainControllers -and -not $Script:TestimoConfiguration.Inclusions.DomainControllers) { Out-Informative -Text 'Following Domain Controllers will be ignored' -Level 0 -Status $null -ExtendedValue ($Script:TestimoConfiguration.Exclusions.DomainControllers -join ', ') } $ForestDetails = Get-WinADForestDetails -Forest $ForestName -ExcludeDomains $ExcludeDomains -IncludeDomains $IncludeDomains -IncludeDomainControllers $IncludeDomainControllers -ExcludeDomainControllers $ExcludeDomainControllers -SkipRODC:$SkipRODC -Extended $null = Start-Testing -Scope 'Forest' -ForestInformation $ForestDetails.Forest { foreach ($Domain in $ForestDetails.Domains) { $Script:Reporting['Domains'][$Domain] = [ordered] @{} $Script:Reporting['Domains'][$Domain]['Summary'] = [ordered] @{} $Script:Reporting['Domains'][$Domain]['Tests'] = [ordered] @{} $Script:Reporting['Domains'][$Domain]['DomainControllers'] = [ordered] @{} if ($ForestDetails['DomainsExtended']["$Domain"]) { Start-Testing -Scope 'Domain' -Domain $Domain -DomainInformation $ForestDetails['DomainsExtended']["$Domain"] -ForestInformation $ForestDetails.Forest { if (Get-TestimoSourcesStatus -Scope 'DomainControllers') { foreach ($DC in $ForestDetails['DomainDomainControllers'][$Domain]) { $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DC.Name] = [ordered] @{} $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DC.Name]['Summary'] = [ordered] @{} $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DC.Name]['Tests'] = [ordered] @{} Start-Testing -Scope 'DomainControllers' -Domain $Domain -DomainController $DC.Name -IsPDC $DC.IsPDC -DomainInformation $ForestDetails['DomainsExtended']["$Domain"] -ForestInformation $ForestDetails.Forest } } } } } } $Script:Reporting['Results'] = $Script:TestResults if ($ReturnResults -and $ExtendedResults) { $Script:Reporting } else { if ($ReturnResults) { $Script:TestResults } } if ($ReportPath -or $ShowReport) { Start-TestimoReport -FilePath $ReportPath -Online:$Online -ShowHTML:$ShowReport -TestResults $Script:Reporting } } [scriptblock] $SourcesAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $ForestKeys = $Script:TestimoConfiguration.Forest.Keys $DomainKeys = $Script:TestimoConfiguration.Domain.Keys $DomainControllerKeys = $Script:TestimoConfiguration.DomainControllers.Keys $TestSources = @(foreach ($Key in $ForestKeys) { "Forest$Key" } foreach ($Key in $DomainKeys) { "Domain$Key" } foreach ($Key in $DomainControllerKeys) { "DC$Key" }) $TestSources | Sort-Object | Where-Object { $_ -like "$wordToComplete*" } } Register-ArgumentCompleter -CommandName Invoke-Testimo -ParameterName Sources -ScriptBlock $SourcesAutoCompleter Register-ArgumentCompleter -CommandName Invoke-Testimo -ParameterName ExcludeSources -ScriptBlock $SourcesAutoCompleter Register-ArgumentCompleter -CommandName Get-TestimoSources -ParameterName Sources -ScriptBlock $SourcesAutoCompleter Export-ModuleMember -Function @('Get-TestimoConfiguration', 'Get-TestimoSources', 'Import-PrivateModule', 'Invoke-Testimo') -Alias @('Test-IMO', 'Test-ImoAD') |