PSWinDocumentation.AD.psm1

function Clear-DataInformation { 
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Data,
        [Array] $TypesRequired,
        [switch] $DontRemoveSupportData,
        [switch] $DontRemoveEmpty)
    foreach ($Domain in $Data.FoundDomains.Keys) {
        $RemoveDomainKeys = foreach ($Key in $Data.FoundDomains.$Domain.Keys) {
            if ($null -eq $Data.FoundDomains.$Domain.$Key) {
                if (-not $DontRemoveEmpty) { $Key }
                continue
            }
            if ($Key -notin $TypesRequired -and $DontRemoveSupportData -eq $false) { $Key }
        }
        foreach ($Key in $RemoveDomainKeys) { $Data.FoundDomains.$Domain.Remove($Key) }
    }
    $RemoveDomains = foreach ($Domain in $Data.FoundDomains.Keys) { if ($Data.FoundDomains.$Domain.Count -eq 0) { $Domain } }
    foreach ($Domain in $RemoveDomains) { $Data.FoundDomains.Remove($Domain) }
    if ($Data.FoundDomains.Count -eq 0) { $Data.Remove('FoundDomains') }
    $RemoveKeys = foreach ($Key in $Data.Keys) {
        if ($Key -eq 'FoundDomains') { continue }
        if ($null -eq $Data.$Key) {
            if (-not $DontRemoveEmpty) { $Key }
            continue
        }
        if ($Key -notin $TypesRequired -and $DontRemoveSupportData -eq $false) { $Key }
    }
    foreach ($Key in $RemoveKeys) { $Data.Remove($Key) }
}
function ConvertFrom-DistinguishedName { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER DistinguishedName
    Parameter description
 
    .PARAMETER ToOrganizationalUnit
    Parameter description
 
    .PARAMETER ToDC
    Parameter description
 
    .PARAMETER ToDomainCN
    Parameter description
 
    .EXAMPLE
    $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz'
    ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToOrganizationalUnit
 
    Output:
    OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz
 
    .EXAMPLE
    $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz'
    ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName
 
    Output:
    Przemyslaw Klys
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName,
        [switch] $ToOrganizationalUnit,
        [switch] $ToDC,
        [switch] $ToDomainCN)
    process {
        foreach ($Distinguished in $DistinguishedName) {
            if ($ToDomainCN) {
                $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1'
                $CN = $DN -replace ',DC=', '.' -replace "DC="
                $CN
            } elseif ($ToOrganizationalUnit) { [Regex]::Match($Distinguished, '(?=OU=)(.*\n?)(?<=.)').Value } elseif ($ToDC) { $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' } else {
                $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$'
                $Output = foreach ($_ in $Distinguished) {
                    $_ -match $Regex
                    $Matches
                }
                $Output.cn
            }
        }
    }
}
function Convert-TimeToDays { 
    [CmdletBinding()]
    param ($StartTime,
        $EndTime,
        [string] $Ignore = '*1601*')
    if ($null -ne $StartTime -and $null -ne $EndTime) { try { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start $StartTime -End $EndTime).Days } } catch {} } elseif ($null -ne $EndTime) { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start (Get-Date) -End ($EndTime)).Days } } elseif ($null -ne $StartTime) { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start $StartTime -End (Get-Date)).Days } }
    return $Days
}
function Convert-ToDateTime { 
    [CmdletBinding()]
    param ([string] $Timestring,
        [string] $Ignore = '*1601*')
    Try { $DateTime = ([datetime]::FromFileTime($Timestring)) } catch { $DateTime = $null }
    if ($null -eq $DateTime -or $DateTime -like $Ignore) { return $null } else { return $DateTime }
}
function ConvertTo-OperatingSystem { 
    <#
    .SYNOPSIS
    Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD
 
    .DESCRIPTION
    Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD
 
    .PARAMETER OperatingSystem
    Operating System as returned by Active Directory
 
    .PARAMETER OperatingSystemVersion
    Operating System Version as returned by Active Directory
 
    .EXAMPLE
    $Computers = Get-ADComputer -Filter * -Properties OperatingSystem, OperatingSystemVersion | ForEach-Object {
        $OPS = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
        Add-Member -MemberType NoteProperty -Name 'OperatingSystemTranslated' -Value $OPS -InputObject $_ -Force
        $_
    }
    $Computers | Select-Object DNS*, Name, SamAccountName, Enabled, OperatingSystem*, DistinguishedName | Format-Table
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string] $OperatingSystem,
        [string] $OperatingSystemVersion)
    if ($OperatingSystem -like '*Windows 10*') {
        $Systems = @{'10.0 (19042)' = 'Windows 10 Insider Preview Build 19042.421 (20H2)'
            '10.0 (19041)'          = 'Windows 10 2004'
            '10.0 (18363)'          = "Windows 10 1909"
            '10.0 (18362)'          = "Windows 10 1903"
            '10.0 (17763)'          = "Windows 10 1809"
            '10.0 (17134)'          = "Windows 10 1803"
            '10.0 (16299)'          = "Windows 10 1709"
            '10.0 (15063)'          = "Windows 10 1703"
            '10.0 (14393)'          = "Windows 10 1607"
            '10.0 (10586)'          = "Windows 10 1511"
            '10.0 (10240)'          = "Windows 10 1507"
            '10.0 (18898)'          = 'Windows 10 Insider Preview'
            '10.0.19042'            = 'Windows 10 Insider Preview Build 19042.421 (20H2)'
            '10.0.19041'            = 'Windows 10 2004'
            '10.0.18363'            = "Windows 10 1909"
            '10.0.18362'            = "Windows 10 1903"
            '10.0.17763'            = "Windows 10 1809"
            '10.0.17134'            = "Windows 10 1803"
            '10.0.16299'            = "Windows 10 1709"
            '10.0.15063'            = "Windows 10 1703"
            '10.0.14393'            = "Windows 10 1607"
            '10.0.10586'            = "Windows 10 1511"
            '10.0.10240'            = "Windows 10 1507"
            '10.0.18898'            = 'Windows 10 Insider Preview'
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) { $System = $OperatingSystem }
    } elseif ($OperatingSystem -like '*Windows Server*') {
        $Systems = @{'5.2 (3790)' = 'Windows Server 2003'
            '6.1 (7601)'          = 'Windows Server 2008 R2'
            '10.0 (18362)'        = "Windows Server, version 1903 (Semi-Annual Channel) 1903"
            '10.0 (17763)'        = "Windows Server 2019 (Long-Term Servicing Channel) 1809"
            '10.0 (17134)'        = "Windows Server, version 1803 (Semi-Annual Channel) 1803"
            '10.0 (14393)'        = "Windows Server 2016 (Long-Term Servicing Channel) 1607"
            '10.0.18362'          = "Windows Server, version 1903 (Semi-Annual Channel) 1903"
            '10.0.17763'          = "Windows Server 2019 (Long-Term Servicing Channel) 1809"
            '10.0.17134'          = "Windows Server, version 1803 (Semi-Annual Channel) 1803"
            '10.0.14393'          = "Windows Server 2016 (Long-Term Servicing Channel) 1607"
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) { $System = $OperatingSystem }
    } else { $System = $OperatingSystem }
    if ($System) { $System } else { 'Unknown' }
}
function Find-TypesNeeded { 
    [CmdletBinding()]
    param ([Array] $TypesRequired,
        [Array] $TypesNeeded)
    [bool] $Found = $False
    foreach ($Type in $TypesNeeded) {
        if ($TypesRequired -contains $Type) {
            $Found = $true
            break
        }
    }
    return $Found
}
function Get-DataInformation { 
    [CmdletBinding()]
    param([ScriptBlock] $Content,
        [string] $Text,
        [Array] $TypesRequired,
        [Array] $TypesNeeded,
        [Array] $Commands,
        [switch] $SkipAvailability)
    if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded $TypesNeeded) {
        Write-Verbose -Message $Text
        $Time = Start-TimeLog
        if ($Commands.Count -gt 0 -and -not $SkipAvailability) {
            $Available = Test-AvailabilityCommands -Commands $Commands
            if ($Available -contains $false) {
                $EndTime = Stop-TimeLog -Time $Time -Option OneLiner
                Write-Warning "Get-DataInformation - Commands $($Commands -join ", ") is/are not available. Data gathering skipped."
                Write-Verbose "$Text - Time: $EndTime"
                return
            }
        }
        if ($null -ne $Content) { & $Content }
        $EndTime = Stop-TimeLog -Time $Time -Option OneLiner
        Write-Verbose "$Text - Time: $EndTime"
    }
}
function Get-ObjectCount { 
    [CmdletBinding()]
    param([parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)][Object]$Object)
    return $($Object | Measure-Object).Count
}
function Get-Types { 
    [CmdletBinding()]
    param ([Object] $Types)
    $TypesRequired = foreach ($Type in $Types) { $Type.GetEnumValues() }
    return $TypesRequired
}
function Get-WinADForestGUIDs { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Domain
    Parameter description
 
    .PARAMETER RootDSE
    Parameter description
 
    .PARAMETER DisplayNameKey
    Parameter description
 
    .EXAMPLE
    Get-WinADForestGUIDs
 
    .EXAMPLE
    Get-WinADForestGUIDs -DisplayNameKey
 
    .NOTES
    General notes
    #>

    [alias('Get-WinADDomainGUIDs')]
    [cmdletbinding()]
    param([string] $Domain = $Env:USERDNSDOMAIN,
        [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE,
        [switch] $DisplayNameKey)
    if ($null -eq $RootDSE) { $RootDSE = Get-ADRootDSE -Server $Domain }
    $GUID = @{}
    $GUID.Add('00000000-0000-0000-0000-000000000000', 'All')
    $Schema = Get-ADObject -SearchBase $RootDSE.schemaNamingContext -LDAPFilter '(schemaIDGUID=*)' -Properties name, schemaIDGUID
    foreach ($S in $Schema) { if ($DisplayNameKey) { $GUID["$($S.name)"] = $(([System.GUID]$S.schemaIDGUID).Guid) } else { $GUID["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.name } }
    $Extended = Get-ADObject -SearchBase "CN=Extended-Rights,$($RootDSE.configurationNamingContext)" -LDAPFilter '(objectClass=controlAccessRight)' -Properties name, rightsGUID
    foreach ($S in $Extended) { if ($DisplayNameKey) { $GUID["$($S.name)"] = $(([System.GUID]$S.rightsGUID).Guid) } else { $GUID["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.name } }
    return $GUID
}
function Get-WinADForestControllers { 
    [alias('Get-WinADDomainControllers')]
    <#
    .SYNOPSIS
 
    .DESCRIPTION
    Long description
 
    .PARAMETER TestAvailability
    Parameter description
 
    .EXAMPLE
    Get-WinADForestControllers -TestAvailability | Format-Table
 
    .EXAMPLE
    Get-WinADDomainControllers
 
    .EXAMPLE
    Get-WinADDomainControllers | Format-Table *
 
    Output:
 
    Domain HostName Forest IPV4Address IsGlobalCatalog IsReadOnly SchemaMaster DomainNamingMasterMaster PDCEmulator RIDMaster InfrastructureMaster Comment
    ------ -------- ------ ----------- --------------- ---------- ------------ ------------------------ ----------- --------- -------------------- -------
    ad.evotec.xyz AD1.ad.evotec.xyz ad.evotec.xyz 192.168.240.189 True False True True True True True
    ad.evotec.xyz AD2.ad.evotec.xyz ad.evotec.xyz 192.168.240.192 True False False False False False False
    ad.evotec.pl ad.evotec.xyz False False False False False Unable to contact the server. This may be becau...
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string[]] $Domain,
        [switch] $TestAvailability,
        [switch] $SkipEmpty)
    try {
        $Forest = Get-ADForest
        if (-not $Domain) { $Domain = $Forest.Domains }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        Write-Warning "Get-WinADForestControllers - Couldn't use Get-ADForest feature. Error: $ErrorMessage"
        return
    }
    $Servers = foreach ($D in $Domain) {
        try {
            $LocalServer = Get-ADDomainController -Discover -DomainName $D -ErrorAction Stop -Writable
            $DC = Get-ADDomainController -Server $LocalServer.HostName[0] -Filter * -ErrorAction Stop
            foreach ($S in $DC) {
                $Server = [ordered] @{Domain = $D
                    HostName                 = $S.HostName
                    Name                     = $S.Name
                    Forest                   = $Forest.RootDomain
                    IPV4Address              = $S.IPV4Address
                    IPV6Address              = $S.IPV6Address
                    IsGlobalCatalog          = $S.IsGlobalCatalog
                    IsReadOnly               = $S.IsReadOnly
                    Site                     = $S.Site
                    SchemaMaster             = ($S.OperationMasterRoles -contains 'SchemaMaster')
                    DomainNamingMaster       = ($S.OperationMasterRoles -contains 'DomainNamingMaster')
                    PDCEmulator              = ($S.OperationMasterRoles -contains 'PDCEmulator')
                    RIDMaster                = ($S.OperationMasterRoles -contains 'RIDMaster')
                    InfrastructureMaster     = ($S.OperationMasterRoles -contains 'InfrastructureMaster')
                    LdapPort                 = $S.LdapPort
                    SslPort                  = $S.SslPort
                    Pingable                 = $null
                    Comment                  = ''
                }
                if ($TestAvailability) { $Server['Pingable'] = foreach ($_ in $Server.IPV4Address) { Test-Connection -Count 1 -Server $_ -Quiet -ErrorAction SilentlyContinue } }
                [PSCustomObject] $Server
            }
        } catch {
            [PSCustomObject]@{Domain     = $D
                HostName                 = ''
                Name                     = ''
                Forest                   = $Forest.RootDomain
                IPV4Address              = ''
                IPV6Address              = ''
                IsGlobalCatalog          = ''
                IsReadOnly               = ''
                Site                     = ''
                SchemaMaster             = $false
                DomainNamingMasterMaster = $false
                PDCEmulator              = $false
                RIDMaster                = $false
                InfrastructureMaster     = $false
                LdapPort                 = ''
                SslPort                  = ''
                Pingable                 = $null
                Comment                  = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            }
        }
    }
    if ($SkipEmpty) { return $Servers | Where-Object { $_.HostName -ne '' } }
    return $Servers
}
function Get-WinADForestDetails { 
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [string] $Filter = '*',
        [switch] $TestAvailability,
        [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All',
        [int[]] $Ports = 135,
        [int] $PortsTimeout = 100,
        [int] $PingCount = 1,
        [switch] $Extended,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    if ($Global:ProgressPreference -ne 'SilentlyContinue') {
        $TemporaryProgress = $Global:ProgressPreference
        $Global:ProgressPreference = 'SilentlyContinue'
    }
    if (-not $ExtendedForestInformation) {
        $Findings = [ordered] @{}
        try { if ($Forest) { $ForestInformation = Get-ADForest -ErrorAction Stop -Identity $Forest } else { $ForestInformation = Get-ADForest -ErrorAction Stop } } catch {
            Write-Warning "Get-WinADForestDetails - Error discovering DC for Forest - $($_.Exception.Message)"
            return
        }
        if (-not $ForestInformation) { return }
        $Findings['Forest'] = $ForestInformation
        $Findings['ForestDomainControllers'] = @()
        $Findings['QueryServers'] = @{}
        $Findings['DomainDomainControllers'] = @{}
        [Array] $Findings['Domains'] = foreach ($_ in $ForestInformation.Domains) {
            if ($IncludeDomains) {
                if ($_ -in $IncludeDomains) { $_.ToLower() }
                continue
            }
            if ($_ -notin $ExcludeDomains) { $_.ToLower() }
        }
        foreach ($Domain in $ForestInformation.Domains) {
            try {
                $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop
                $OrderedDC = [ordered] @{Domain = $DC.Domain
                    Forest                      = $DC.Forest
                    HostName                    = [Array] $DC.HostName
                    IPv4Address                 = $DC.IPv4Address
                    IPv6Address                 = $DC.IPv6Address
                    Name                        = $DC.Name
                    Site                        = $DC.Site
                }
            } catch {
                Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)"
                continue
            }
            if ($Domain -eq $Findings['Forest']['Name']) { $Findings['QueryServers']['Forest'] = $OrderedDC }
            $Findings['QueryServers']["$Domain"] = $OrderedDC
        }
        [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) {
            $QueryServer = $Findings['QueryServers'][$Domain]['HostName'][0]
            [Array] $AllDC = try {
                try { $DomainControllers = Get-ADDomainController -Filter $Filter -Server $QueryServer -ErrorAction Stop } catch {
                    Write-Warning "Get-WinADForestDetails - Error listing DCs for domain $Domain - $($_.Exception.Message)"
                    continue
                }
                foreach ($S in $DomainControllers) {
                    if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } }
                    if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } }
                    $Server = [ordered] @{Domain = $Domain
                        HostName                 = $S.HostName
                        Name                     = $S.Name
                        Forest                   = $ForestInformation.RootDomain
                        Site                     = $S.Site
                        IPV4Address              = $S.IPV4Address
                        IPV6Address              = $S.IPV6Address
                        IsGlobalCatalog          = $S.IsGlobalCatalog
                        IsReadOnly               = $S.IsReadOnly
                        IsSchemaMaster           = ($S.OperationMasterRoles -contains 'SchemaMaster')
                        IsDomainNamingMaster     = ($S.OperationMasterRoles -contains 'DomainNamingMaster')
                        IsPDC                    = ($S.OperationMasterRoles -contains 'PDCEmulator')
                        IsRIDMaster              = ($S.OperationMasterRoles -contains 'RIDMaster')
                        IsInfrastructureMaster   = ($S.OperationMasterRoles -contains 'InfrastructureMaster')
                        OperatingSystem          = $S.OperatingSystem
                        OperatingSystemVersion   = $S.OperatingSystemVersion
                        OperatingSystemLong      = ConvertTo-OperatingSystem -OperatingSystem $S.OperatingSystem -OperatingSystemVersion $S.OperatingSystemVersion
                        LdapPort                 = $S.LdapPort
                        SslPort                  = $S.SslPort
                        DistinguishedName        = $S.ComputerObjectDN
                        Pingable                 = $null
                        WinRM                    = $null
                        PortOpen                 = $null
                        Comment                  = ''
                    }
                    if ($TestAvailability) {
                        if ($Test -eq 'All' -or $Test -like 'Ping*') { $Server.Pingable = Test-Connection -ComputerName $Server.IPV4Address -Quiet -Count $PingCount }
                        if ($Test -eq 'All' -or $Test -like '*WinRM*') { $Server.WinRM = (Test-WinRM -ComputerName $Server.HostName).Status }
                        if ($Test -eq 'All' -or '*PortOpen*') { $Server.PortOpen = (Test-ComputerPort -Server $Server.HostName -PortTCP $Ports -Timeout $PortsTimeout).Status }
                    }
                    [PSCustomObject] $Server
                }
            } catch {
                [PSCustomObject]@{Domain     = $Domain
                    HostName                 = ''
                    Name                     = ''
                    Forest                   = $ForestInformation.RootDomain
                    IPV4Address              = ''
                    IPV6Address              = ''
                    IsGlobalCatalog          = ''
                    IsReadOnly               = ''
                    Site                     = ''
                    SchemaMaster             = $false
                    DomainNamingMasterMaster = $false
                    PDCEmulator              = $false
                    RIDMaster                = $false
                    InfrastructureMaster     = $false
                    LdapPort                 = ''
                    SslPort                  = ''
                    DistinguishedName        = ''
                    Pingable                 = $null
                    WinRM                    = $null
                    PortOpen                 = $null
                    Comment                  = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                }
            }
            if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC }
            [Array] $Findings['DomainDomainControllers'][$Domain]
        }
        if ($Extended) {
            $Findings['DomainsExtended'] = @{}
            $Findings['DomainsExtendedNetBIOS'] = @{}
            foreach ($DomainEx in $Findings['Domains']) {
                try {
                    $Findings['DomainsExtended'][$DomainEx] = Get-ADDomain -Server $Findings['QueryServers'][$DomainEx].HostName[0] | ForEach-Object { [ordered] @{AllowedDNSSuffixes = $_.AllowedDNSSuffixes | ForEach-Object -Process { $_ }
                            ChildDomains                                                                                                                                              = $_.ChildDomains | ForEach-Object -Process { $_ }
                            ComputersContainer                                                                                                                                        = $_.ComputersContainer
                            DeletedObjectsContainer                                                                                                                                   = $_.DeletedObjectsContainer
                            DistinguishedName                                                                                                                                         = $_.DistinguishedName
                            DNSRoot                                                                                                                                                   = $_.DNSRoot
                            DomainControllersContainer                                                                                                                                = $_.DomainControllersContainer
                            DomainMode                                                                                                                                                = $_.DomainMode
                            DomainSID                                                                                                                                                 = $_.DomainSID.Value
                            ForeignSecurityPrincipalsContainer                                                                                                                        = $_.ForeignSecurityPrincipalsContainer
                            Forest                                                                                                                                                    = $_.Forest
                            InfrastructureMaster                                                                                                                                      = $_.InfrastructureMaster
                            LastLogonReplicationInterval                                                                                                                              = $_.LastLogonReplicationInterval
                            LinkedGroupPolicyObjects                                                                                                                                  = $_.LinkedGroupPolicyObjects | ForEach-Object -Process { $_ }
                            LostAndFoundContainer                                                                                                                                     = $_.LostAndFoundContainer
                            ManagedBy                                                                                                                                                 = $_.ManagedBy
                            Name                                                                                                                                                      = $_.Name
                            NetBIOSName                                                                                                                                               = $_.NetBIOSName
                            ObjectClass                                                                                                                                               = $_.ObjectClass
                            ObjectGUID                                                                                                                                                = $_.ObjectGUID
                            ParentDomain                                                                                                                                              = $_.ParentDomain
                            PDCEmulator                                                                                                                                               = $_.PDCEmulator
                            PublicKeyRequiredPasswordRolling                                                                                                                          = $_.PublicKeyRequiredPasswordRolling | ForEach-Object -Process { $_ }
                            QuotasContainer                                                                                                                                           = $_.QuotasContainer
                            ReadOnlyReplicaDirectoryServers                                                                                                                           = $_.ReadOnlyReplicaDirectoryServers | ForEach-Object -Process { $_ }
                            ReplicaDirectoryServers                                                                                                                                   = $_.ReplicaDirectoryServers | ForEach-Object -Process { $_ }
                            RIDMaster                                                                                                                                                 = $_.RIDMaster
                            SubordinateReferences                                                                                                                                     = $_.SubordinateReferences | ForEach-Object -Process { $_ }
                            SystemsContainer                                                                                                                                          = $_.SystemsContainer
                            UsersContainer                                                                                                                                            = $_.UsersContainer
                        } }
                    $NetBios = $Findings['DomainsExtended'][$DomainEx]['NetBIOSName']
                    $Findings['DomainsExtendedNetBIOS'][$NetBios] = $Findings['DomainsExtended'][$DomainEx]
                } catch {
                    Write-Warning "Get-WinADForestDetails - Error gathering Domain Information for domain $DomainEx - $($_.Exception.Message)"
                    continue
                }
            }
        }
        if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress }
        $Findings
    } else {
        $Findings = Copy-DictionaryManual -Dictionary $ExtendedForestInformation
        [Array] $Findings['Domains'] = foreach ($_ in $Findings.Domains) {
            if ($IncludeDomains) {
                if ($_ -in $IncludeDomains) { $_.ToLower() }
                continue
            }
            if ($_ -notin $ExcludeDomains) { $_.ToLower() }
        }
        foreach ($_ in [string[]] $Findings.DomainDomainControllers.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainDomainControllers.Remove($_) } }
        foreach ($_ in [string[]] $Findings.DomainsExtended.Keys) {
            if ($_ -notin $Findings.Domains) {
                $Findings.DomainsExtended.Remove($_)
                $NetBiosName = $Findings.DomainsExtended.$_.'NetBIOSName'
                if ($NetBiosName) { $Findings.DomainsExtendedNetBIOS.Remove($NetBiosName) }
            }
        }
        [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) {
            [Array] $AllDC = foreach ($S in $Findings.DomainDomainControllers["$Domain"]) {
                if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } }
                if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } }
                $S
            }
            if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC }
            [Array] $Findings['DomainDomainControllers'][$Domain]
        }
        $Findings
    }
}
function Start-TimeLog { 
    [CmdletBinding()]
    param()
    [System.Diagnostics.Stopwatch]::StartNew()
}
function Stop-TimeLog { 
    [CmdletBinding()]
    param ([Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time,
        [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner',
        [switch] $Continue)
    Begin {}
    Process { if ($Option -eq 'Array') { $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds" } else { $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" } }
    End {
        if (-not $Continue) { $Time.Stop() }
        return $TimeToExecute
    }
}
function Copy-DictionaryManual { 
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Dictionary)
    $clone = @{}
    foreach ($Key in $Dictionary.Keys) {
        $value = $Dictionary.$Key
        $clonedValue = switch ($Dictionary.$Key) {
            { $null -eq $_ } {
                $null
                continue
            }
            { $_ -is [System.Collections.IDictionary] } {
                Copy-DictionaryManual -Dictionary $_
                continue
            }
            { $type = $_.GetType()
                $type.IsPrimitive -or $type.IsValueType -or $_ -is [string] } {
                $_
                continue
            }
            default { $_ | Select-Object -Property * }
        }
        if ($value -is [System.Collections.IList]) { $clone[$Key] = @($clonedValue) } else { $clone[$Key] = $clonedValue }
    }
    $clone
}
function Test-AvailabilityCommands { 
    [cmdletBinding()]
    param ([string[]] $Commands)
    $CommandsStatus = foreach ($Command in $Commands) {
        $Exists = Get-Command -Name $Command -ErrorAction SilentlyContinue
        if ($Exists) { Write-Verbose "Test-AvailabilityCommands - Command $Command is available." } else { Write-Verbose "Test-AvailabilityCommands - Command $Command is not available." }
        $Exists
    }
    return $CommandsStatus
}
function Test-ComputerPort { 
    [CmdletBinding()]
    param ([alias('Server')][string[]] $ComputerName,
        [int[]] $PortTCP,
        [int[]] $PortUDP,
        [int]$Timeout = 5000)
    begin {
        if ($Global:ProgressPreference -ne 'SilentlyContinue') {
            $TemporaryProgress = $Global:ProgressPreference
            $Global:ProgressPreference = 'SilentlyContinue'
        }
    }
    process {
        foreach ($Computer in $ComputerName) {
            foreach ($P in $PortTCP) {
                $Output = [ordered] @{'ComputerName' = $Computer
                    'Port'                           = $P
                    'Protocol'                       = 'TCP'
                    'Status'                         = $null
                    'Summary'                        = $null
                    'Response'                       = $null
                }
                $TcpClient = Test-NetConnection -ComputerName $Computer -Port $P -InformationLevel Detailed -WarningAction SilentlyContinue
                if ($TcpClient.TcpTestSucceeded) {
                    $Output['Status'] = $TcpClient.TcpTestSucceeded
                    $Output['Summary'] = "TCP $P Successful"
                } else {
                    $Output['Status'] = $false
                    $Output['Summary'] = "TCP $P Failed"
                    $Output['Response'] = $Warnings
                }
                [PSCustomObject]$Output
            }
            foreach ($P in $PortUDP) {
                $Output = [ordered] @{'ComputerName' = $Computer
                    'Port'                           = $P
                    'Protocol'                       = 'UDP'
                    'Status'                         = $null
                    'Summary'                        = $null
                }
                $UdpClient = [System.Net.Sockets.UdpClient]::new($Computer, $P)
                $UdpClient.Client.ReceiveTimeout = $Timeout
                $Encoding = [System.Text.ASCIIEncoding]::new()
                $byte = $Encoding.GetBytes("Evotec")
                [void]$UdpClient.Send($byte, $byte.length)
                $RemoteEndpoint = [System.Net.IPEndPoint]::new([System.Net.IPAddress]::Any, 0)
                try {
                    $Bytes = $UdpClient.Receive([ref]$RemoteEndpoint)
                    [string]$Data = $Encoding.GetString($Bytes)
                    If ($Data) {
                        $Output['Status'] = $true
                        $Output['Summary'] = "UDP $P Successful"
                        $Output['Response'] = $Data
                    }
                } catch {
                    $Output['Status'] = $false
                    $Output['Summary'] = "UDP $P Failed"
                    $Output['Response'] = $_.Exception.Message
                }
                $UdpClient.Close()
                $UdpClient.Dispose()
                [PSCustomObject]$Output
            }
        }
    }
    end { if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } }
}
function Test-WinRM { 
    [CmdletBinding()]
    param ([alias('Server')][string[]] $ComputerName)
    $Output = foreach ($Computer in $ComputerName) {
        $Test = [PSCustomObject] @{Output = $null
            Status                        = $null
            ComputerName                  = $Computer
        }
        try {
            $Test.Output = Test-WSMan -ComputerName $Computer -ErrorAction Stop
            $Test.Status = $true
        } catch { $Test.Status = $false }
        $Test
    }
    $Output
}
Add-Type -TypeDefinition @"
    using System;
 
    namespace PSWinDocumentation
    {
        [Flags]
        public enum ActiveDirectory {
            // Forest Information - Section Main
            ForestInformation,
            ForestFSMO,
            ForestRoles,
            ForestGlobalCatalogs,
            ForestOptionalFeatures,
            ForestUPNSuffixes,
            ForestSPNSuffixes,
            ForestSites,
            ForestSites1,
            ForestSites2,
            ForestSubnets,
            ForestSubnets1,
            ForestSubnets2,
            ForestSiteLinks,
            ForestDomainControllers,
            ForestRootDSE,
            ForestSchemaPropertiesUsers,
            ForestSchemaPropertiesComputers,
            ForestReplication,
 
            // Domain Information - Section Main
            DomainRootDSE,
            DomainRIDs,
            DomainAuthenticationPolicies, // Not yet tested
            DomainAuthenticationPolicySilos, // Not yet tested
            DomainCentralAccessPolicies, // Not yet tested
            DomainCentralAccessRules, // Not yet tested
            DomainClaimTransformPolicies, // Not yet tested
            DomainClaimTypes, // Not yet tested
            DomainFineGrainedPolicies,
            DomainFineGrainedPoliciesUsers,
            DomainFineGrainedPoliciesUsersExtended,
            DomainGUIDS,
            DomainDNSSRV,
            DomainDNSA,
            DomainInformation,
            DomainControllers,
            DomainFSMO,
            DomainDefaultPasswordPolicy,
            DomainGroupPolicies,
            DomainGroupPoliciesDetails,
            DomainGroupPoliciesACL,
            DomainGroupPoliciesACLConsistency,
            DomainGroupPoliciesSysVol,
            DomainGroupPoliciesOwners,
            DomainGroupPoliciesWMI,
            DomainGroupPoliciesLinksSummary,
            DomainOrganizationalUnits,
            DomainOrganizationalUnitsBasicACL,
            DomainOrganizationalUnitsExtendedACL,
            DomainContainers,
            DomainTrustsClean,
            DomainTrusts,
 
            DomainWellKnownFolders,
 
            DomainBitlocker,
            DomainLAPS,
 
            // Domain Information - Group Data
            DomainGroupsFullList, // Contains all data
 
            DomainGroups,
            DomainGroupsMembers,
            DomainGroupsMembersRecursive,
 
            DomainGroupsSpecial,
            DomainGroupsSpecialMembers,
            DomainGroupsSpecialMembersRecursive,
 
            DomainGroupsPriviliged,
            DomainGroupsPriviligedMembers,
            DomainGroupsPriviligedMembersRecursive,
 
            // Domain Information - User Data
            DomainUsersFullList, // Contains all data
            DomainUsers,
            DomainUsersCount,
            DomainUsersAll,
            DomainUsersSystemAccounts,
            DomainUsersNeverExpiring,
            DomainUsersNeverExpiringInclDisabled,
            DomainUsersExpiredInclDisabled,
            DomainUsersExpiredExclDisabled,
            DomainAdministrators,
            DomainAdministratorsRecursive,
            DomainEnterpriseAdministrators,
            DomainEnterpriseAdministratorsRecursive,
 
            // Domain Information - Computer Data
            DomainComputersFullList, // Contains all data
            DomainComputersAll,
            DomainComputersAllBuildCount,
            DomainComputersAllCount,
            DomainComputers,
            DomainComputersCount,
            DomainServers,
            DomainServersCount,
            DomainComputersUnknown,
            DomainComputersUnknownCount,
 
            // This requires DSInstall PowerShell Module
            DomainPasswordDataUsers, // Gathers users data and their passwords
            DomainPasswordDataPasswords, // Compares Users Password with File
            DomainPasswordDataPasswordsHashes, // Compares Users Password with File HASH
            DomainPasswordClearTextPassword, // include both enabled / disabled accounts
            DomainPasswordClearTextPasswordEnabled, // include only enabled
            DomainPasswordClearTextPasswordDisabled, // include only disabled
            DomainPasswordLMHash,
            DomainPasswordEmptyPassword,
            DomainPasswordWeakPassword,
            DomainPasswordWeakPasswordEnabled,
            DomainPasswordWeakPasswordDisabled,
            DomainPasswordWeakPasswordList, // Password List from file..
            DomainPasswordDefaultComputerPassword,
            DomainPasswordPasswordNotRequired,
            DomainPasswordPasswordNeverExpires,
            DomainPasswordSmartCardUsersWithPassword,
            DomainPasswordAESKeysMissing,
            DomainPasswordPreAuthNotRequired,
            DomainPasswordDESEncryptionOnly,
            DomainPasswordDelegatableAdmins,
            DomainPasswordDuplicatePasswordGroups,
            DomainPasswordHashesWeakPassword,
            DomainPasswordHashesWeakPasswordEnabled,
            DomainPasswordHashesWeakPasswordDisabled,
            DomainPasswordStats
        }
    }
"@

function Get-ADObjectFromDNHash {
    [CmdletBinding()]
    param (
        [string[]] $DistinguishedName,
        [hashtable] $ADCatalog,
        [string] $Type = '',
        [string] $Splitter # ', ' # Alternative for example [System.Environment]::NewLine
    )
    if ($null -eq $DistinguishedName) {
        return
    }
    $FoundObjects = foreach ($DN in $DistinguishedName) {
        if ($Type -eq '') {
            $ADCatalog.$DN
        } else {
            $ADCatalog.$DN.$Type
        }
    }
    if ($Splitter) {
        return ($FoundObjects | Sort-Object) -join $Splitter
    } else {
        return $FoundObjects | Sort-Object
    }
}
function Get-WinADDomain {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN
    )
    try {
        Get-ADDomain -Server $Domain -ErrorAction Stop
    } catch {
        $null
    }
}
function Get-WinADDomainAdministrators {
    [CmdletBinding()]
    param(
        [Array] $DomainGroupsMembers,
        $DomainInformation
    )
    # $DomainGroupsMembers | Where-Object { $_.'Group SID' -eq $('{0}-512' -f $DomainInformation.DomainSID.Value) } | Select-Object * -Exclude Group*, 'High Privileged Group'

    $Members = foreach ($_ in $DomainGroupsMembers) {
        if ($_.'Group SID' -eq $('{0}-512' -f $DomainInformation.DomainSID.Value)) {
            $_
        }
    }
    $Members | Select-Object * -Exclude Group*, 'High Privileged Group'
}
function Get-WinADDomainAdministratorsRecursive {
    [CmdletBinding()]
    param(
        [Array] $DomainGroupsMembersRecursive,
        $DomainInformation
    )
    # $DomainGroupsMembersRecursive | Where-Object { $_.'Group SID' -eq $('{0}-512' -f $DomainInformation.DomainSID.Value) } | Select-Object * -Exclude Group*, 'High Privileged Group'

    $Members = foreach ($_ in $DomainGroupsMembersRecursive) {
        if ($_.'Group SID' -eq $('{0}-512' -f $DomainInformation.DomainSID.Value)) {
            $_
        }
    }
    $Members | Select-Object * -Exclude Group*, 'High Privileged Group'
}
function Get-WinADDomainAllUsersCount {
    [CmdletBinding()]
    param(
        [Array] $DomainUsers,
        [Array] $DomainUsersAll,
        [Array] $DomainUsersExpiredExclDisabled,
        [Array] $DomainUsersExpiredInclDisabled,
        [Array] $DomainUsersNeverExpiring,
        [Array] $DomainUsersNeverExpiringInclDisabled,
        [Array] $DomainUsersSystemAccounts
    )
    <#
    $DomainUsersCount = [ordered] @{
        'Users Count Incl. System' = Get-ObjectCount -Object $DomainUsers
        'Users Count' = Get-ObjectCount -Object $DomainUsersAll
        'Users Expired' = Get-ObjectCount -Object $DomainUsersExpiredExclDisabled
        'Users Expired Incl. Disabled' = Get-ObjectCount -Object $DomainUsersExpiredInclDisabled
        'Users Never Expiring' = Get-ObjectCount -Object $DomainUsersNeverExpiring
        'Users Never Expiring Incl. Disabled' = Get-ObjectCount -Object $DomainUsersNeverExpiringInclDisabled
        'Users System Accounts' = Get-ObjectCount -Object $DomainUsersSystemAccounts
    }
    #>

    $DomainUsersCount = [ordered] @{
        'Users Count Incl. System'            = $DomainUsers.Count
        'Users Count'                         = $DomainUsersAll.Count
        'Users Expired'                       = $DomainUsersExpiredExclDisabled.Count
        'Users Expired Incl. Disabled'        = $DomainUsersExpiredInclDisabled.Count
        'Users Never Expiring'                = $DomainUsersNeverExpiring.Count
        'Users Never Expiring Incl. Disabled' = $DomainUsersNeverExpiringInclDisabled.Count
        'Users System Accounts'               = $DomainUsersSystemAccounts.Count
    }
    return $DomainUsersCount
}

function Get-WinADDomainBitlocker {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN,
        [Array] $Computers
    )
    $Properties = @(
        'Name',
        'OperatingSystem',
        'DistinguishedName'
    )
    if ($null -eq $Computers) {
        $Computers = Get-ADComputer -Filter * -Properties $Properties -Server $Domain
    }
    foreach ($Computer in $Computers) {
        try {
            $Bitlockers = Get-ADObject -Filter 'objectClass -eq "msFVE-RecoveryInformation"' -SearchBase $Computer.DistinguishedName -Properties 'WhenCreated', 'msFVE-RecoveryPassword' #| Sort-Object whenCreated -Descending #| Select-Object whenCreated, msFVE-RecoveryPassword
        } catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            if ($ErrorMessage -like "*The supplied distinguishedName must belong to one of the following partition(s)*") {
                Write-Warning "Getting domain information - $Domain - Couldn't get Bitlocker information. Most likely not enabled."
            } else {
                Write-Warning "Getting domain information - $Domain - Couldn't get Bitlocker information. Error: $ErrorMessage"
            }
            return
        }
        foreach ($Bitlocker in $Bitlockers) {
            [PSCustomObject] @{
                'Name'                        = $Computer.Name
                'Operating System'            = $Computer.'OperatingSystem'
                'Bitlocker Recovery Password' = $Bitlocker.'msFVE-RecoveryPassword'
                'Bitlocker When'              = $Bitlocker.WhenCreated
                'DistinguishedName'           = $Computer.'DistinguishedName'
            }
        }
    }
}
function Get-WinADDomainComputers {
    [CmdletBinding()]
    param(
        [Array] $DomainComputersAll
    )
    #$DomainComputersAll | & { process { if ($_.OperatingSystem -notlike 'Windows Server*' -and $null -ne $_.OperatingSystem) { $_ } } } # | Where-Object { $_.OperatingSystem -notlike 'Windows Server*' -and $_.OperatingSystem -ne $null }

    foreach ($_ in $DomainComputersAll) {
        if ($_.OperatingSystem -notlike 'Windows Server*' -and $null -ne $_.OperatingSystem) {
            $_
        }
    }
}
function Get-WinADDomainComputersAll {
    [CmdletBinding()]
    param(
        [Array] $DomainComputersFullList,
        [string] $Splitter,
        [System.Collections.IDictionary] $DomainObjects,
        [System.Collections.IDictionary] $DomainObjectsNetbios,
        [Object] $Domaininformation

    )
    [DateTime] $CurrentDate = Get-Date
    foreach ($_ in $DomainComputersFullList) {
        $Manager = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $_.ManagedBy
        $Computer = [PSCustomObject] @{
            SamAccountName              = $_.SamAccountName
            Enabled                     = $_.Enabled
            OperatingSystem             = $_.OperatingSystem
            PasswordLastSet             = $_.PasswordLastSet
            'PasswordLastChanged(Days)' = if ($null -ne $_.PasswordLastSet) { "$(-$($_.PasswordLastSet - $CurrentDate).Days)" } else { }
            IPv4Address                 = $_.IPv4Address
            IPv6Address                 = $_.IPv6Address
            Name                        = $_.Name
            DNSHostName                 = $_.DNSHostName
            'Manager'                   = $Manager.Name
            'ManagerEmail'              = if ($Splitter -ne '') { $Manager.EmailAddress -join $Splitter } else { $Manager.EmailAddress }
            OperatingSystemVersion      = $_.OperatingSystemVersion
            OperatingSystemHotfix       = $_.OperatingSystemHotfix
            OperatingSystemServicePack  = $_.OperatingSystemServicePack
            OperatingSystemBuild        = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
            PasswordNeverExpires        = $_.PasswordNeverExpires
            PasswordNotRequired         = $_.PasswordNotRequired
            UserPrincipalName           = $_.UserPrincipalName
            LastLogonDate               = $_.LastLogonDate
            'LastLogonDate(Days)'       = if ($null -ne $_.LastLogonDate) { "$(-$($_.LastLogonDate - $CurrentDate).Days)" } else { }
            LockedOut                   = $_.LockedOut
            LogonCount                  = $_.LogonCount
            CanonicalName               = $_.CanonicalName
            SID                         = $_.SID
            Created                     = $_.Created
            Modified                    = $_.Modified
            Deleted                     = $_.Deleted
            "Protected"                 = $_.ProtectedFromAccidentalDeletion
            "PrimaryGroup"              = (Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $_.PrimaryGroup -Type 'SamAccountName')
            "MemberOf"                  = (Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $_.MemberOf -Type 'SamAccountName' -Splitter $Splitter)
        }
        $Name = -join ($Domaininformation.NetBIOSName, "\", $Computer.SamAccountName)
        $DomainObjectsNetbios[$Name] = $Computer
        $Computer
    }
}
function Get-WinADDomainComputersAllBuildSummary {
    [CmdletBinding()]
    param(
        [Array] $DomainComputers,
        [switch] $Formatted
    )
    if ($Formatted) {
        $DomainComputers | Group-Object -Property OperatingSystemBuild | Sort-Object -Property Name | `
                Select-Object @{ L = 'System Name'; Expression = { if ($_.Name -ne '') { $_.Name } else { 'N/A' } } } , @{ L = 'System Count'; Expression = { $_.Count } }
    } else {
        $DomainComputers | Group-Object -Property OperatingSystemBuild | Sort-Object -Property Name
    }
}
function Get-WinADDomainComputersAllCount {
    [CmdletBinding()]
    param(
        [Array] $DomainComputersAll
    )
    $DomainComputersAll | `
            Group-Object -Property OperatingSystem | `
                Select-Object @{ L = 'System Name'; Expression = { if ($_.Name -ne '') { $_.Name } else { 'Unknown' } } } , @{ L = 'System Count'; Expression = { $_.Count } }

}
function Get-WinADDomainComputersCount {
    [CmdletBinding()]
    param(
        [Array] $DomainComputers
    )
    $DomainComputers | Group-Object -Property OperatingSystem | Select-Object @{ L = 'System Name'; Expression = { if ($_.Name -ne '') { $_.Name } else { 'N/A' } } } , @{ L = 'System Count'; Expression = { $_.Count } }
}
function Get-WinADDomainComputersFullList {
    [cmdletbinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN,
        [Array] $ForestSchemaComputers,
        [System.Collections.IDictionary] $DomainObjects,
        [int] $ResultPageSize = 500000
    )
    if ($Extended) {
        [string] $Properties = '*'
    } else {
        [string[]] $Properties = @(
            'SamAccountName', 'Enabled', 'OperatingSystem',
            'PasswordLastSet', 'IPv4Address', 'IPv6Address', 'Name', 'DNSHostName',
            'ManagedBy', 'OperatingSystemVersion', 'OperatingSystemHotfix',
            'OperatingSystemServicePack' , 'PasswordNeverExpires',
            'PasswordNotRequired', 'UserPrincipalName',
            'LastLogonDate', 'LockedOut', 'LogonCount',
            'CanonicalName', 'SID', 'Created', 'Modified',
            'Deleted', 'MemberOf', 'PrimaryGroup', 'ProtectedFromAccidentalDeletion'
            if ($ForestSchemaComputers.Name -contains 'ms-Mcs-AdmPwd') {
                'ms-Mcs-AdmPwd'
                'ms-Mcs-AdmPwdExpirationTime'
            }
        )
    }
    $Computers = Get-ADComputer -Server $Domain -Filter * -ResultPageSize $ResultPageSize -Properties $Properties -ErrorAction SilentlyContinue #| Select-Object -Property $Properties -ExcludeProperty $ExcludeProperty
    if ($null -ne $DomainObjects) {
        foreach ($_ in $Computers) {
            $DomainObjects[$_.DistinguishedName] = $_
        }
    }
    $Computers
}
function Get-WinADDomainComputersUnknown {
    [CmdletBinding()]
    param(
        [Array] $DomainComputersAll
    )
    #$DomainComputersAll | & { process { if ( $null -eq $_.OperatingSystem ) { $_ } } } # | Where-Object { $_.OperatingSystem -eq $null }
    foreach ($_ in $DomainComputersAll) {
        if ( $null -eq $_.OperatingSystem ) {
            $_
        }
    }
}
function Get-WinADDomainComputersUnknownCount {
    [CmdletBinding()]
    param(
        [Array] $DomainComputersUnknown
    )
    $DomainComputersUnknown | Group-Object -Property OperatingSystem | Select-Object @{ L = 'System Name'; Expression = { if ($_.Name -ne '') { $_.Name } else { 'Unknown' } } } , @{ L = 'System Count'; Expression = { $_.Count } }
}
function Get-WinADDomainControllersInternal {
    [CmdletBinding()]
    param(
        [string] $Domain
    )
    $DomainControllersClean = Get-ADDomainController -Server $Domain -Filter *
    foreach ($DC in $DomainControllersClean) {
        [PsCustomObject] @{
            'Name'             = $DC.Name
            'Host Name'        = $DC.HostName
            'Operating System' = $DC.OperatingSystem
            'Site'             = $DC.Site
            'Ipv4'             = $DC.Ipv4Address
            'Ipv6'             = $DC.Ipv6Address
            'Global Catalog?'  = $DC.IsGlobalCatalog
            'Read Only?'       = $DC.IsReadOnly
            'Ldap Port'        = $DC.LdapPort
            'SSL Port'         = $DC.SSLPort
        }
    }
}
function Get-WinADDomainDefaultPasswordPolicy {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN
    )
    $Policy = Get-ADDefaultDomainPasswordPolicy -Server $Domain

    [ordered] @{
        'Complexity Enabled'            = $Policy.ComplexityEnabled
        'Lockout Duration'              = ($Policy.LockoutDuration).TotalMinutes
        'Lockout Observation Window'    = ($Policy.LockoutObservationWindow).TotalMinutes
        'Lockout Threshold'             = $Policy.LockoutThreshold
        'Max Password Age'              = $($Policy.MaxPasswordAge).TotalDays
        'Min Password Length'           = $Policy.MinPasswordLength
        'Min Password Age'              = $($Policy.MinPasswordAge).TotalDays
        'Password History Count'        = $Policy.PasswordHistoryCount
        'Reversible Encryption Enabled' = $Policy.ReversibleEncryptionEnabled
        'Distinguished Name'            = $Policy.DistinguishedName
    }
}
function Get-WinADDomainDNSData {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN
    )
    $DnsRecords = "_kerberos._tcp.$Domain", "_ldap._tcp.$Domain"
    $DNSData = foreach ($DnsRecord in $DnsRecords) {
        $Value = Resolve-DnsName -Name $DnsRecord -Type SRV -Verbose:$false -ErrorAction SilentlyContinue #| Select-Object *
        if ($null -eq $Value) {
            Write-Warning 'Getting domain information - DomainDNSSRV / DomainDNSA - Failed!'
        }
        $Value
    }
    $ReturnData = @{}
    $ReturnData.Srv = foreach ($V in $DNSData) {
        if ($V.QueryType -eq 'SRV') {
            $V | Select-Object Target, NameTarget, Priority, Weight, Port, Name
        }
    }

    $ReturnData.A = foreach ($V in $DNSData) {
        if ($V.QueryType -ne 'SRV') {
            $V | Select-Object Address, IPAddress, IP4Address, Name, Type, DataLength, TTL
        }
    }
    return $ReturnData
}
function Get-WinADDomainEnterpriseAdministrators {
    [CmdletBinding()]
    param(
        [Array] $DomainGroupsMembers,
        $DomainInformation
    )
    #$DomainGroupsMembers | Where-Object { $_.'Group SID' -eq $('{0}-519' -f $DomainInformation.DomainSID.Value) } | Select-Object * -Exclude Group*, 'High Privileged Group'

    $Members = foreach ($_ in $DomainGroupsMembers) {
        if ($_.'Group SID' -eq $('{0}-519' -f $DomainInformation.DomainSID.Value)) {
            $_
        }
    }
    $Members | Select-Object * -Exclude Group*, 'High Privileged Group'
}
function Get-WinADDomainEnterpriseAdministratorsRecursive {
    [CmdletBinding()]
    param(
        [Array] $DomainGroupsMembersRecursive,
        $DomainInformation
    )
    # $DomainGroupsMembersRecursive | Where-Object { $_.'Group SID' -eq $('{0}-519' -f $DomainInformation.DomainSID.Value) } | Select-Object * -Exclude Group*, 'High Privileged Group'
    $Members = foreach ($_ in $DomainGroupsMembersRecursive) {
        if ($_.'Group SID' -eq $('{0}-519' -f $DomainInformation.DomainSID.Value)) {
            $_
        }
    }
    $Members | Select-Object * -Exclude Group*, 'High Privileged Group'
}
function Get-WinADDomainFineGrainedPolicies {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN
    )
    $FineGrainedPoliciesData = Get-ADFineGrainedPasswordPolicy -Filter * -Server $Domain
    $FineGrainedPolicies = foreach ($Policy in $FineGrainedPoliciesData) {
        [PsCustomObject] @{
            'Name'                          = $Policy.Name
            'Complexity Enabled'            = $Policy.ComplexityEnabled
            'Lockout Duration'              = $Policy.LockoutDuration
            'Lockout Observation Window'    = $Policy.LockoutObservationWindow
            'Lockout Threshold'             = $Policy.LockoutThreshold
            'Max Password Age'              = $Policy.MaxPasswordAge
            'Min Password Length'           = $Policy.MinPasswordLength
            'Min Password Age'              = $Policy.MinPasswordAge
            'Password History Count'        = $Policy.PasswordHistoryCount
            'Reversible Encryption Enabled' = $Policy.ReversibleEncryptionEnabled
            'Precedence'                    = $Policy.Precedence
            'Applies To'                    = $Policy.AppliesTo # get all groups / usrs and convert to data TODO
            'Distinguished Name'            = $Policy.DistinguishedName
        }
    }
    return $FineGrainedPolicies

}
function Get-WinADDomainFineGrainedPoliciesUsers {
    [CmdletBinding()]
    param(
        [Array] $DomainFineGrainedPolicies,
        #[Array] $DomainUsersFullList,
        #[Array] $DomainGroupsFullList,
        [hashtable] $DomainObjects
    )

    $PolicyUsers = foreach ($Policy in $DomainFineGrainedPolicies) {
        $AllObjects = foreach ($U in $Policy.'Applies To') {
            Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $U
            #Get-ADObjectFromDistingusishedName -ADCatalog $DomainUsersFullList -DistinguishedName $U
        }
        #$Groups = foreach ($U in $Policy.'Applies To') {
        # Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $U
        #Get-ADObjectFromDistingusishedName -ADCatalog $DomainGroupsFullList -DistinguishedName $U
        #}
        foreach ($_ in $AllObjects) {
            [PsCustomObject] @{
                'Policy Name'  = $Policy.Name
                Name           = $_.Name
                SamAccountName = $_.SamAccountName
                Type           = $_.ObjectClass
                SID            = $_.SID
            }
        }
        <#
        foreach ($Group in $Groups) {
            [PsCustomObject] @{
                'Policy Name' = $Policy.Name
                Name = $Group.Name
                SamAccountName = $Group.SamAccountName
                Type = $Group.ObjectClass
                SID = $Group.SID
            }
        }
        #>

    }
    #Get-AdFineGrainedPassowrdPolicySubject
    #Get-AdresultantPasswordPolicy -Identity <user>
    return $PolicyUsers
}
function Get-WinADDomainFineGrainedPoliciesUsersExtended {
    [CmdletBinding()]
    param(
        [Array] $DomainFineGrainedPolicies,
        # [Array] $DomainUsersFullList,
        # [Array] $DomainGroupsFullList,
        [string] $Domain = ($Env:USERDNSDOMAIN).ToLower(),
        [hashtable] $DomainObjects
    )
    $CurrentDate = Get-Date
    $PolicyUsers = @(
        foreach ($Policy in $DomainFineGrainedPolicies) {
            $Objects = foreach ($U in $Policy.'Applies To') {
                #Get-ADObjectFromDistingusishedName -ADCatalog $DomainUsersFullList -DistinguishedName $U
                Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $U
            }
            $Users = foreach ($_ in $Objects) {
                if ($_.ObjectClass -eq 'user') {
                    $_
                }
            }
            $Groups = foreach ($_ in $Objects) {
                if ($_.ObjectClass -eq 'group') {
                    $_
                }
            }
            foreach ($User in $Users) {
                $Manager = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $User.Manager

                [PsCustomObject] @{
                    'Policy Name'                       = $Policy.Name
                    Name                                = $User.Name
                    SamAccountName                      = $User.SamAccountName
                    Type                                = $User.ObjectClass
                    SID                                 = $User.SID
                    'High Privileged Group'             = 'N/A'
                    'Display Name'                      = $User.DisplayName
                    'Member Name'                       = $Member.Name
                    'User Principal Name'               = $User.UserPrincipalName
                    'Sam Account Name'                  = $User.SamAccountName
                    'Email Address'                     = $User.EmailAddress
                    'PasswordExpired'                   = $User.PasswordExpired
                    'PasswordLastSet'                   = $User.PasswordLastSet
                    'PasswordNotRequired'               = $User.PasswordNotRequired
                    'PasswordNeverExpires'              = $User.PasswordNeverExpires
                    'Enabled'                           = $User.Enabled
                    'MemberSID'                         = $Member.SID.Value
                    #'Manager' = (Get-ADObjectFromDistingusishedName -ADCatalog $DomainUsersFullList -DistinguishedName $User.Manager).Name
                    #'ManagerEmail' = (Get-ADObjectFromDistingusishedName -ADCatalog $DomainUsersFullList -DistinguishedName $User.Manager).EmailAddress
                    'Manager'                           = $Manager.Name
                    'ManagerEmail'                      = if ($Splitter -ne '') { $Manager.EmailAddress -join $Splitter } else { $Manager.EmailAddress }
                    'DateExpiry'                        = Convert-ToDateTime -Timestring $($Object."msDS-UserPasswordExpiryTimeComputed") # -Verbose
                    "DaysToExpire"                      = (Convert-TimeToDays -StartTime ($CurrentDate) -EndTime (Convert-ToDateTime -Timestring $($User."msDS-UserPasswordExpiryTimeComputed")))
                    "AccountExpirationDate"             = $User.AccountExpirationDate
                    "AccountLockoutTime"                = $User.AccountLockoutTime
                    "AllowReversiblePasswordEncryption" = $User.AllowReversiblePasswordEncryption
                    "BadLogonCount"                     = $User.BadLogonCount
                    "CannotChangePassword"              = $User.CannotChangePassword
                    "CanonicalName"                     = $User.CanonicalName
                    'Given Name'                        = $User.GivenName
                    'Surname'                           = $User.Surname
                    "Description"                       = $User.Description
                    "DistinguishedName"                 = $User.DistinguishedName
                    "EmployeeID"                        = $User.EmployeeID
                    "EmployeeNumber"                    = $User.EmployeeNumber
                    "LastBadPasswordAttempt"            = $User.LastBadPasswordAttempt
                    "LastLogonDate"                     = $User.LastLogonDate
                    "Created"                           = $User.Created
                    "Modified"                          = $User.Modified
                    "Protected"                         = $User.ProtectedFromAccidentalDeletion
                    "Domain"                            = $Domain
                }
            }
            #$Groups = foreach ($U in $Policy.'Applies To') {
            # Get-ADObjectFromDistingusishedName -ADCatalog $DomainGroupsFullList -DistinguishedName $U
            # Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $U
            #}
            foreach ($Group in $Groups) {
                $GroupMembership = Get-ADGroupMember -Server $Domain -Identity $Group.SID -Recursive
                foreach ($Member in $GroupMembership) {
                    # $Object = (Get-ADObjectFromDistingusishedName -ADCatalog $DomainUsersFullList -DistinguishedName $Member.DistinguishedName)
                    $Object = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $Member.DistinguishedName
                    $Manager = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $Object.Manager
                    [PsCustomObject] @{
                        'Policy Name'                       = $Policy.Name
                        Name                                = $Group.Name
                        SamAccountName                      = $Group.SamAccountName
                        Type                                = $Group.ObjectClass
                        SID                                 = $Group.SID
                        'High Privileged Group'             = if ($Group.adminCount -eq 1) { $True } else { $False }
                        'Display Name'                      = $Object.DisplayName
                        'Member Name'                       = $Member.Name
                        'User Principal Name'               = $Object.UserPrincipalName
                        'Sam Account Name'                  = $Object.SamAccountName
                        'Email Address'                     = $Object.EmailAddress
                        'PasswordExpired'                   = $Object.PasswordExpired
                        'PasswordLastSet'                   = $Object.PasswordLastSet
                        'PasswordNotRequired'               = $Object.PasswordNotRequired
                        'PasswordNeverExpires'              = $Object.PasswordNeverExpires
                        'Enabled'                           = $Object.Enabled
                        'MemberSID'                         = $Member.SID.Value
                        # 'Manager' = (Get-ADObjectFromDistingusishedName -ADCatalog $DomainUsersFullList -DistinguishedName $Object.Manager).Name
                        # 'ManagerEmail' = (Get-ADObjectFromDistingusishedName -ADCatalog $DomainUsersFullList -DistinguishedName $Object.Manager).EmailAddress
                        'Manager'                           = $Manager.Name
                        'ManagerEmail'                      = if ($Splitter -ne '') { $Manager.EmailAddress -join $Splitter } else { $Manager.EmailAddress }
                        'DateExpiry'                        = Convert-ToDateTime -Timestring $($Object."msDS-UserPasswordExpiryTimeComputed") # -Verbose
                        "DaysToExpire"                      = (Convert-TimeToDays -StartTime ($CurrentDate) -EndTime (Convert-ToDateTime -Timestring $($Object."msDS-UserPasswordExpiryTimeComputed")))
                        "AccountExpirationDate"             = $Object.AccountExpirationDate
                        "AccountLockoutTime"                = $Object.AccountLockoutTime
                        "AllowReversiblePasswordEncryption" = $Object.AllowReversiblePasswordEncryption
                        "BadLogonCount"                     = $Object.BadLogonCount
                        "CannotChangePassword"              = $Object.CannotChangePassword
                        "CanonicalName"                     = $Object.CanonicalName
                        'Given Name'                        = $Object.GivenName
                        'Surname'                           = $Object.Surname
                        "Description"                       = $Object.Description
                        "DistinguishedName"                 = $Object.DistinguishedName
                        "EmployeeID"                        = $Object.EmployeeID
                        "EmployeeNumber"                    = $Object.EmployeeNumber
                        "LastBadPasswordAttempt"            = $Object.LastBadPasswordAttempt
                        "LastLogonDate"                     = $Object.LastLogonDate
                        "Created"                           = $Object.Created
                        "Modified"                          = $Object.Modified
                        "Protected"                         = $Object.ProtectedFromAccidentalDeletion
                        "Domain"                            = $Domain
                    }
                }
            }
        }
    )
    return $PolicyUsers
}
function Get-WinADDomainFSMO {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN,
        [Microsoft.ActiveDirectory.Management.ADDomain] $DomainInformation
    )
    # required for multiple use cases FSMO/DomainTrusts
    [ordered] @{
        'PDC Emulator'          = $DomainInformation.PDCEmulator
        'RID Master'            = $DomainInformation.RIDMaster
        'Infrastructure Master' = $DomainInformation.InfrastructureMaster
    }
}
function Get-WinADDomainGroupPolicies {
    [CmdletBinding()]
    param(
        [Array] $GroupPolicies,
        [string] $Domain = $Env:USERDNSDOMAIN
    )
    if ($null -eq $GroupPolicies) {
        $GroupPolicies = Get-GPO -Domain $Domain -All
    }
    foreach ($gpo in $GroupPolicies) {
        [PsCustomObject] @{
            'Display Name'      = $gpo.DisplayName
            'Gpo Status'        = $gpo.GPOStatus
            'Creation Time'     = $gpo.CreationTime
            'Modification Time' = $gpo.ModificationTime
            'Description'       = $gpo.Description
            'Wmi Filter'        = $gpo.WmiFilter
        }
    }
}
function Get-WinADDomainGroupPoliciesACL {
    [CmdletBinding()]
    param(
        [Array] $GroupPolicies,
        [string] $Domain = $Env:USERDNSDOMAIN
    )
    if ($null -eq $GroupPolicies) {
        $GroupPolicies = Get-GPO -Domain $Domain -All
    }
    $Output = ForEach ($GPO in $GroupPolicies) {
        [xml]$XmlGPReport = $GPO.generatereport('xml')
        $ACLs = $XmlGPReport.GPO.SecurityDescriptor.Permissions.TrusteePermissions
        foreach ($ACL in $ACLS) {
            [PsCustomObject] @{
                'GPO Name'        = $GPO.DisplayName
                'User'            = $ACL.trustee.name.'#Text'
                'Permission Type' = $ACL.type.PermissionType
                'Inherited'       = $ACL.Inherited
                'Permissions'     = $ACL.Standard.GPOGroupedAccessEnum
            }
        }
    }
    return $Output
}
function Get-WinADDomainGroupPoliciesDetails {
    [CmdletBinding()]
    param(
        [Array] $GroupPolicies,
        [string] $Domain = $Env:USERDNSDOMAIN,
        [string] $Splitter
    )
    if ($null -eq $GroupPolicies) {
        $GroupPolicies = Get-GPO -Domain $Domain -All
    }
    ForEach ($GPO in $GroupPolicies) {
        [xml]$XmlGPReport = $GPO.generatereport('xml')
        #GPO version
        if ($XmlGPReport.GPO.Computer.VersionDirectory -eq 0 -and $XmlGPReport.GPO.Computer.VersionSysvol -eq 0) {
            $ComputerSettings = "NeverModified"
        } else {
            $ComputerSettings = "Modified"
        }
        if ($XmlGPReport.GPO.User.VersionDirectory -eq 0 -and $XmlGPReport.GPO.User.VersionSysvol -eq 0) {
            $UserSettings = "NeverModified"
        } else {
            $UserSettings = "Modified"
        }
        #GPO content
        if ($null -eq $XmlGPReport.GPO.User.ExtensionData) {
            $UserSettingsConfigured = $false
        } else {
            $UserSettingsConfigured = $true
        }
        if ($null -eq $XmlGPReport.GPO.Computer.ExtensionData) {
            $ComputerSettingsConfigured = $false
        } else {
            $ComputerSettingsConfigured = $true
        }
        #Output
        [PsCustomObject] @{
            'Name'                   = $XmlGPReport.GPO.Name
            'Links'                  = $XmlGPReport.GPO.LinksTo | Select-Object -ExpandProperty SOMPath
            'Has Computer Settings'  = $ComputerSettingsConfigured
            'Has User Settings'      = $UserSettingsConfigured
            'User Enabled'           = $XmlGPReport.GPO.User.Enabled
            'Computer Enabled'       = $XmlGPReport.GPO.Computer.Enabled
            'Computer Settings'      = $ComputerSettings
            'User Settings'          = $UserSettings
            'Gpo Status'             = $GPO.GpoStatus
            'Creation Time'          = $GPO.CreationTime
            'Modification Time'      = $GPO.ModificationTime
            'WMI Filter'             = $GPO.WmiFilter.name
            'WMI Filter Description' = $GPO.WmiFilter.Description
            'Path'                   = $GPO.Path
            'GUID'                   = $GPO.Id
            'SDDL'                   = if ($Splitter -ne '') { $XmlGPReport.GPO.SecurityDescriptor.SDDL.'#text' -join $Splitter } else { $XmlGPReport.GPO.SecurityDescriptor.SDDL.'#text' }
            #'ACLs' = $XmlGPReport.GPO.SecurityDescriptor.Permissions.TrusteePermissions | ForEach-Object -Process {
            # New-Object -TypeName PSObject -Property @{
            # 'User' = $_.trustee.name.'#Text'
            # 'Permission Type' = $_.type.PermissionType
            # 'Inherited' = $_.Inherited
            # 'Permissions' = $_.Standard.GPOGroupedAccessEnum
            # }
            #}
        }
    }
}
function Get-WinADDomainGroupsFullList {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN,
        [HashTable] $DomainObjects,
        [int] $ResultPageSize = 500000
    )
    if ($Extended) {
        [string] $Properties = '*'
    } else {
        [string[]] $Properties = @(
            'adminCount'
            'CanonicalName'
            'CN'
            'Created'
            'createTimeStamp'
            'Deleted'
            'Description'
            'DisplayName'
            'DistinguishedName'
            #'dSCorePropagationData'
            'GroupCategory'
            'GroupScope'
            'groupType'
            'HomePage'
            'instanceType'
            'isCriticalSystemObject'
            'isDeleted'
            'LastKnownParent'
            'ManagedBy'
            'member'
            'MemberOf'
            'Members'
            'Modified'
            'modifyTimeStamp'
            'Name'
            #'nTSecurityDescriptor'
            'ObjectCategory'
            'ObjectClass'
            'ObjectGUID'
            'objectSid'
            'ProtectedFromAccidentalDeletion'
            'SamAccountName'
            'sAMAccountType'
            'sDRightsEffective'
            'SID'
            'SIDHistory'
            'systemFlags'
            'uSNChanged'
            'uSNCreated'
            'whenChanged'
            'whenCreated'
        )
    }
    $Groups = Get-ADGroup -Server $Domain -Filter * -ResultPageSize $ResultPageSize -Properties $Properties
    foreach ($_ in $Groups) {
        # $DomainObjects.$($_.DistinguishedName) = $_
        $DomainObjects.Add($_.DistinguishedName, $_)
    }
    $Groups
}

<#
$Groups = Get-ADGroup -Filter *
$GroupsWithMembers = foreach ($_ in $Groups) {
    $Members = Get-ADGroupMember -Identity $_ -Recursive
    foreach ($Member in $Members) {
        [PSCustomobject] @{
            'Group Name' = $_.Name
            'Member Name' = $Member.Name
            'Member SamAccountName' = $Member.SamAccountName
            'Member UserPrincipalName' = $Member.UserPrincipalName
        }
    }
}
$GroupsWithMembers | Format-Table -AutoSize
#>

function Get-DomainGroupsPriviliged {
    [cmdletbinding()]
    param(
        [Microsoft.ActiveDirectory.Management.ADDomain] $DomainInformation,
        [Array] $DomainGroups
    )
    $PrivilegedGroupsSID = @(
        "S-1-5-32-544"
        "S-1-5-32-548"
        "S-1-5-32-549"
        "S-1-5-32-550"
        "S-1-5-32-551"
        "S-1-5-32-552"
        "S-1-5-32-556"
        "S-1-5-32-557"
        "S-1-5-32-573"
        "S-1-5-32-578"
        "S-1-5-32-580"
        "$($DomainInformation.DomainSID.Value)-512"
        "$($DomainInformation.DomainSID.Value)-518"
        "$($DomainInformation.DomainSID.Value)-519"
        "$($DomainInformation.DomainSID.Value)-520"
    )
    # $DomainGroups | Where-Object { $PrivilegedGroupsSID -contains $_.'Group SID' }
    foreach ($_ in $DomainGroups) {
        if ($PrivilegedGroupsSID -contains $_.'Group SID' ) {
            $_
        }
    }
}
function Get-WinADDomainGroupsPriviligedMembers {
    [CmdletBinding()]
    param(
        [Array] $DomainGroupsMembers,
        [Array] $DomainGroupsPriviliged
    )
    # Needs review
    #$DomainGroupsMembers | Where-Object { $DomainGroupsPriviliged.'Group SID' -contains ($_.'Group SID') } | Select-Object * #-Exclude Group*, 'High Privileged Group'

    foreach ($_ in $DomainGroupsMembers) {
        if ($DomainGroupsPriviliged.'Group SID' -contains ($_.'Group SID')) {
            $_
        }
    }
}
function Get-WinADDomainGroupsPriviligedMembersRecursive {
    [CmdletBinding()]
    param(
        [Array] $DomainGroupsMembersRecursive,
        [Array] $DomainGroupsPriviliged
    )
    # Needs review
    #$DomainGroupsMembersRecursive | Where-Object { $DomainGroupsPriviliged.'Group SID' -contains ($_.'Group SID') } | Select-Object * #-Exclude Group*, 'High Privileged Group'
    foreach ($_ in $DomainGroupsMembersRecursive) {
        if ($DomainGroupsPriviliged.'Group SID' -contains ($_.'Group SID')) {
            $_
        }
    }
}
function Get-WinADDomainGroupsSpecial {
    [CmdletBinding()]
    param(
        [Array] $DomainGroups
    )
    #$DomainGroups | Where-Object { ($_.'Group SID').Length -eq 12 }
    foreach ($_ in $DomainGroups) {
        if (($_.'Group SID').Length -eq 12) {
            $_
        }
    }
}
function Get-WinADDomainGroupsSpecialMembers {
    [CmdletBinding()]
    param(
        [Array] $DomainGroupsMembers
    )
    # $DomainGroupsMembers | Where-Object { ($_.'Group SID').Length -eq 12 } | Select-Object * #-Exclude Group*, 'High Privileged Group'

    foreach ($_ in $DomainGroupsMembers) {
        if (($_.'Group SID').Length -eq 12) {
            $_
        }
    }
}
function Get-WinADDomainGroupsSpecialMembersRecursive {
    [CmdletBinding()]
    param(
        [Array] $DomainGroupsMembersRecursive
    )
    #$DomainGroupsMembersRecursive | Where-Object { ($_.'Group SID').Length -eq 12 } | Select-Object * #-Exclude Group*, 'High Privileged Group'

    foreach ($_ in $DomainGroupsMembersRecursive) {
        if (($_.'Group SID').Length -eq 12) {
            $_
        }
    }
}
function Get-WinADDomainLAPS {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN,
        [Array] $Computers,
        [string] $Splitter
    )
    $Properties = @(
        'Name',
        'OperatingSystem',
        'DistinguishedName',
        'ms-Mcs-AdmPwd',
        'ms-Mcs-AdmPwdExpirationTime'
    )
    [DateTime] $CurrentDate = Get-Date

    if ($null -eq $Computers -or $Computers.Count -eq 0) {
        $Computers = Get-ADComputer -Filter * -Properties $Properties
    }
    foreach ($Computer in $Computers) {
        [PSCustomObject] @{
            'Name'               = $Computer.Name
            'Operating System'   = $Computer.'OperatingSystem'
            'LapsPassword'       = if ($Splitter -ne '') { $Computer.'ms-Mcs-AdmPwd' -join $Splitter } else { $Computer.'ms-Mcs-AdmPwd' } # For some reason it's an array Laps Password : {}
            'LapsExpire(days)'   = Convert-TimeToDays -StartTime ($CurrentDate) -EndTime (Convert-ToDateTime -Timestring ($Computer.'ms-Mcs-AdmPwdExpirationTime'))
            'LapsExpirationTime' = Convert-ToDateTime -Timestring ($Computer.'ms-Mcs-AdmPwdExpirationTime')
            'DistinguishedName'  = $Computer.'DistinguishedName'
        }
    }
}
function Get-WinADDomainOrganizationalUnits {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN,
        [Array] $OrgnaizationalUnits,
        [hashtable] $DomainObjects
    )
    if ($null -eq $OrgnaizationalUnits) {
        $OrgnaizationalUnits = $(Get-ADOrganizationalUnit -Server $Domain -Properties * -Filter * )
    }
    $Output = foreach ($_ in $OrgnaizationalUnits) {
        $Manager = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $_.ManagedBy

        [PSCustomObject] @{
            'Canonical Name'  = $_.CanonicalName
            'Managed'         = $Manager.Name
            'Manager Email'   = $Manager.EmailAddress
            'Protected'       = $_.ProtectedFromAccidentalDeletion
            Description       = $_.Description
            Created           = $_.Created
            Modified          = $_.Modified
            Deleted           = $_.Deleted
            'PostalCode'      = $_.PostalCode
            City              = $_.City
            Country           = $_.Country
            State             = $_.State
            'StreetAddress'   = $_.StreetAddress
            DistinguishedName = $_.DistinguishedName
            ObjectGUID        = $_.ObjectGUID
        }
    }
    $Output | Sort-Object 'Canonical Name'
}
function Get-WinADDomainOrganizationalUnitsACL {
    [cmdletbinding()]
    param(
        [Array] $DomainOrganizationalUnitsClean,
        [string] $Domain = $Env:USERDNSDOMAIN,
        [string] $NetBiosName,
        [string] $RootDomainNamingContext
    )
    # Write-Verbose -Message "Getting domain information - $Domain DomainOrganizationalUnitsBasicACL"
    #$Time = Start-TimeLog
    $OUs = @(
        #@{ Name = 'Root'; Value = $RootDomainNamingContext }
        foreach ($OU in $DomainOrganizationalUnitsClean) {
            @{ Name = 'Organizational Unit'; Value = $OU.DistinguishedName }
        }
    )
    $null = New-PSDrive -Name $NetBiosName -Root '' -PSProvider ActiveDirectory -Server $Domain

    @(
        foreach ($OU in $OUs) {
            $ACL = Get-Acl -Path "$NetBiosName`:\$($OU.Value)"
            [PsCustomObject] @{
                'Distinguished Name'        = $OU.Value
                'Type'                      = $OU.Name
                'Owner'                     = $ACL.Owner
                'Group'                     = $ACL.Group
                'Are AccessRules Protected' = $ACL.AreAccessRulesProtected
                'Are AuditRules Protected'  = $ACL.AreAuditRulesProtected
                'Are AccessRules Canonical' = $ACL.AreAccessRulesCanonical
                'Are AuditRules Canonical'  = $ACL.AreAuditRulesCanonical
                #'Sddl' = $ACL.Sddl
            }
            <#
            Get-Acl -Path "$NetBiosName`:\$($OU.Value)" | Select-Object `
            @{name = 'Distinguished Name'; expression = { $OU.Value } },
            @{name = 'Type'; expression = { $OU.Name } },
            @{name = 'Owner'; expression = { $_.Owner } },
            @{name = 'Group'; expression = { $_.Group } },
            @{name = 'Are AccessRules Protected'; expression = { $_.AreAccessRulesProtected } },
            @{name = 'Are AuditRules Protected'; expression = { $_.AreAuditRulesProtected } },
            @{name = 'Are AccessRules Canonical'; expression = { $_.AreAccessRulesCanonical } },
            @{name = 'Are AuditRules Canonical'; expression = { $_.AreAuditRulesCanonical } },
            @{name = 'Sddl'; expression = { $_.Sddl } }
            #>

        }
    )
    #$EndTime = Stop-TimeLog -Time $Time -Option OneLiner
    # Write-Verbose -Message "Getting domain information - $Domain DomainOrganizationalUnitsBasicACL Time: $EndTime"
}

function Get-WinADDomainOrganizationalUnitsACLExtended {
    [cmdletbinding()]
    param(
        [Array] $DomainOrganizationalUnitsClean,
        [string] $Domain = $Env:USERDNSDOMAIN,
        [string] $NetBiosName,
        [string] $RootDomainNamingContext,
        [hashtable] $GUID
    )
    #Write-Verbose -Message "Getting domain information - $Domain DomainOrganizationalUnitsExtended"
    #$Time = Start-TimeLog
    $OUs = @(
        #@{ Name = 'Root'; Value = $RootDomainNamingContext }
        foreach ($OU in $DomainOrganizationalUnitsClean) {
            @{ Name = 'Organizational Unit'; Value = $OU.DistinguishedName }
        }
    )

    $null = New-PSDrive -Name $NetBiosName -Root '' -PSProvider ActiveDirectory -Server $Domain

    @(
        foreach ($OU in $OUs) {

            $ACLs = Get-Acl -Path "$NetBiosName`:\$($OU.Value)" | Select-Object -ExpandProperty Access
            foreach ($ACL in $ACLs) {
                [PSCustomObject] @{
                    'Distinguished Name'        = $OU.Value
                    'Type'                      = $OU.Name
                    'AccessControlType'         = $ACL.AccessControlType
                    'ObjectType Name'           = if ($ACL.objectType.ToString() -eq '00000000-0000-0000-0000-000000000000') { 'All' } Else { $GUID.Item($ACL.objectType) }
                    'Inherited ObjectType Name' = $GUID.Item($ACL.inheritedObjectType)
                    'ActiveDirectoryRights'     = $ACL.ActiveDirectoryRights
                    'InheritanceType'           = $ACL.InheritanceType
                    'ObjectType'                = $ACL.ObjectType
                    'InheritedObjectType'       = $ACL.InheritedObjectType
                    'ObjectFlags'               = $ACL.ObjectFlags
                    'IdentityReference'         = $ACL.IdentityReference
                    'IsInherited'               = $ACL.IsInherited
                    'InheritanceFlags'          = $ACL.InheritanceFlags
                    'PropagationFlags'          = $ACL.PropagationFlags
                }
            }

            <#
            Get-Acl -Path "$NetBiosName`:\$($OU.Value)" | `
                Select-Object -ExpandProperty Access | `
                Select-Object `
            @{name = 'Distinguished Name'; expression = { $OU.Value } },
            @{name = 'Type'; expression = { $OU.Name } },
            @{name = 'AccessControlType'; expression = { $_.AccessControlType } },
            @{name = 'ObjectType Name'; expression = { if ($_.objectType.ToString() -eq '00000000-0000-0000-0000-000000000000') { 'All' } Else { $GUID.Item($_.objectType) } } },
            @{name = 'Inherited ObjectType Name'; expression = { $GUID.Item($_.inheritedObjectType) } },
            @{name = 'ActiveDirectoryRights'; expression = { $_.ActiveDirectoryRights } },
            @{name = 'InheritanceType'; expression = { $_.InheritanceType } },
            @{name = 'ObjectType'; expression = { $_.ObjectType } },
            @{name = 'InheritedObjectType'; expression = { $_.InheritedObjectType } },
            @{name = 'ObjectFlags'; expression = { $_.ObjectFlags } },
            @{name = 'IdentityReference'; expression = { $_.IdentityReference } },
            @{name = 'IsInherited'; expression = { $_.IsInherited } },
            @{name = 'InheritanceFlags'; expression = { $_.InheritanceFlags } },
            @{name = 'PropagationFlags'; expression = { $_.PropagationFlags } }
 
            #>


        }
    )
    #$EndTime = Stop-TimeLog -Time $Time -Option OneLiner
    #Write-Verbose -Message "Getting domain information - $Domain DomainOrganizationalUnitsExtended Time: $EndTime"
}

<#
$Data = @{}
$Domain = 'ad.evotec.xyz'
$Data.DomainRootDSE = $(Get-ADRootDSE -Server $Domain)
$Data.DomainInformation = $(Get-ADDomain -Server $Domain)
$Data.DomainGUIDS = Invoke-Command -ScriptBlock {
    $GUID = @{ }
    Get-ADObject -SearchBase (Get-ADRootDSE).schemaNamingContext -LDAPFilter '(schemaIDGUID=*)' -Properties name, schemaIDGUID | ForEach-Object {
        if ($GUID.Keys -notcontains $_.schemaIDGUID ) {
            $GUID.add([System.GUID]$_.schemaIDGUID, $_.name)
        }
    }
    Get-ADObject -SearchBase "CN=Extended-Rights,$((Get-ADRootDSE).configurationNamingContext)" -LDAPFilter '(objectClass=controlAccessRight)' -Properties name, rightsGUID | ForEach-Object {
        if ($GUID.Keys -notcontains $_.rightsGUID ) {
            $GUID.add([System.GUID]$_.rightsGUID, $_.name)
        }
    }
    return $GUID
}
$OU = $(Get-ADOrganizationalUnit -Server $Domain -Properties * -Filter * )
 
 
Get-WinADDomainOrganizationalUnitsACLExtended `
    -DomainOrganizationalUnitsClean $OU `
    -Domain $Domain `
    -NetBiosName $Data.DomainInformation.NetBIOSName `
    -RootDomainNamingContext $Data.DomainRootDSE.rootDomainNamingContext `
    -GUID $Data.DomainGUIDS
 
    #>

function Get-WinADDomainPassword {
    [CmdletBinding()]
    param(
        [alias('DnsRoot')][string] $Server,
        [alias('DomainDN')][string] $DistinguishedName
    )
    try {
        Get-ADReplAccount -All -Server $Server -NamingContext $DistinguishedName
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        if ($ErrorMessage -like '*is not recognized as the name of a cmdlet*') {
            Write-Warning "Get-ADReplAccount - Please install module DSInternals (Install-Module DSInternals) - Error: $ErrorMessage"
        } else {
            Write-Warning "Get-ADReplAccount - Error occured: $ErrorMessage"
        }
    }
}
function Get-WinADDomainPasswordQuality {
    [CmdletBinding()]
    param (
        [string] $DnsRoot,
        [string] $DomainDistinguishedName,

        [Array] $PasswordQualityUsers,
        [string] $FilePath,
        [switch] $UseHashes,
        [switch] $PasswordQuality,
        [Array] $Properties,
        [System.Collections.IDictionary] $DomainObjectsNetbios
    )
    if (-not $DomainObjectsNetbios) {
        Write-Warning "Get-WinADDomainPasswordQuality - DomainobjectsNetbios not passed. Creating new one, but this will skip tests."
        $DomainObjectsNetbios = [ordered] @{}
    }

    if (-not $Properties) {
        $Properties = @(
            'Name', 'UserPrincipalName', 'Enabled', 'Password Last Changed', "DaysToExpire",
            'PasswordExpired', 'PasswordNeverExpires', 'PasswordNotRequired', 'DateExpiry', 'PasswordLastSet', 'SamAccountName',
            'EmailAddress', 'Display Name', 'Given Name', 'Surname', 'Manager', 'Manager Email',
            "AccountExpirationDate", "AccountLockoutTime", "AllowReversiblePasswordEncryption", "BadLogonCount",
            "CannotChangePassword", "CanonicalName", "Description", "DistinguishedName", "EmployeeID", "EmployeeNumber", "LastBadPasswordAttempt",
            "LastLogonDate", "Created", "Modified", "Protected", "Primary Group", "Member Of", "Domain"
        )
    }

    if ($FilePath -eq '' -and $PasswordQuality.IsPresent -eq $true) {
        $FilePath = "$PSScriptRoot\Resources\PasswordList.txt"
    }

    if ($FilePath -eq '') {
        Write-Verbose "Get-WinADDomainPasswordQuality - File path not given, using hashes set to $($UseHashes.IsPresent)"
        return $null
    }
    if (-not (Test-Path -Path $FilePath)) {
        Write-Verbose "Get-WinADDomainPasswordQuality - File path doesn't exists, using hashes set to $($UseHashes.IsPresent)"
        return $null
    }
    $Data = [ordered] @{ }
    if ($PasswordQualityUsers) {
        $Data.PasswordQualityUsers = $PasswordQualityUsers
    } else {
        $Data.PasswordQualityUsers = Get-ADReplAccount -All -Server $DnsRoot -NamingContext $DomainDistinguishedName
    }
    $Data.PasswordQuality = Invoke-Command -ScriptBlock {
        if ($UseHashes) {
            $Results = $Data.PasswordQualityUsers | Test-PasswordQuality -WeakPasswordHashesFile $FilePath -IncludeDisabledAccounts
        } else {
            $Results = $Data.PasswordQualityUsers | Test-PasswordQuality -WeakPasswordsFile $FilePath -IncludeDisabledAccounts
        }
        return $Results
    }
    $Data.DomainPasswordClearTextPassword = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.ClearTextPassword) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }
    $Data.DomainPasswordClearTextPasswordEnabled = Invoke-Command -ScriptBlock {
        return $Data.DomainPasswordClearTextPassword | Where-Object { $_.Enabled -eq $true }
    }
    $Data.DomainPasswordClearTextPasswordDisabled = Invoke-Command -ScriptBlock {
        return $Data.DomainPasswordClearTextPassword | Where-Object { $_.Enabled -eq $false }
    }
    $Data.DomainPasswordLMHash = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.LMHash) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }
    $Data.DomainPasswordEmptyPassword = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.EmptyPassword) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }
    $Data.DomainPasswordWeakPassword = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.WeakPassword) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }
    $Data.DomainPasswordWeakPasswordEnabled = Invoke-Command -ScriptBlock {
        return $Data.DomainPasswordWeakPassword | Where-Object { $_.Enabled -eq $true }
    }
    $Data.DomainPasswordWeakPasswordDisabled = Invoke-Command -ScriptBlock {
        return $Data.DomainPasswordWeakPassword | Where-Object { $_.Enabled -eq $false }
    }
    $Data.DomainPasswordWeakPasswordList = Invoke-Command -ScriptBlock {
        if ($UseHashes) {
            return ''
        } else {
            $Passwords = Get-Content -Path $FilePath
            return $Passwords -join ', '
        }
    }
    $Data.DomainPasswordAESKeysMissing = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.AESKeysMissing) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }
    $Data.DomainPasswordDefaultComputerPassword = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.DefaultComputerPassword) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }
    $Data.DomainPasswordPasswordNotRequired = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.PasswordNotRequired) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }
    $Data.DomainPasswordPasswordNeverExpires = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.PasswordNeverExpires) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }
    $Data.DomainPasswordPreAuthNotRequired = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.PreAuthNotRequired) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }
    $Data.DomainPasswordDESEncryptionOnly = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.DESEncryptionOnly) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }
    $Data.DomainPasswordDelegatableAdmins = Invoke-Command -ScriptBlock {
        foreach ($User in $Data.PasswordQuality.DelegatableAdmins) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }

    $Data.DomainPasswordSmartCardUsersWithPassword = & {
        foreach ($User in $Data.PasswordQuality.SmartCardUsersWithPassword) {
            if ($DomainObjectsNetbios["$User"]) {
                $DomainObjectsNetbios["$User"]
            } else {
                Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
            }
        }
    }

    $Data.DomainPasswordDuplicatePasswordGroups = Invoke-Command -ScriptBlock {
        $DuplicateGroups = $Data.PasswordQuality.DuplicatePasswordGroups.ToArray()
        $Count = 0
        foreach ($DuplicateGroup in $DuplicateGroups) {
            $Count++
            $Name = "Duplicate $Count"
            foreach ($User in $DuplicateGroup) {
                if ($DomainObjectsNetbios["$User"]) {
                    Add-Member -InputObject $DomainObjectsNetbios["$User"] -MemberType NoteProperty -Name 'Duplicate Group' -Value $Name -Force
                    $DomainObjectsNetbios["$User"]
                } else {
                    Write-Warning "Get-WinADDomainPasswordQuality - Couldn't find object $User in cache."
                }
            }
        }
    }
    return $Data
}
function Get-WinADDomainPasswordStats {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary]$PasswordsQuality,
        [Array] $TypesRequired,
        $DomainPasswordHashesWeakPassword,
        $DomainPasswordHashesWeakPasswordEnabled,
        $DomainPasswordHashesWeakPasswordDisabled
    )

    $Stats = [ordered] @{ }
    $Stats.'Clear Text Passwords' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordClearTextPassword
    $Stats.'LM Hashes' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordLMHash
    $Stats.'Empty Passwords' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordEmptyPassword
    $Stats.'Weak Passwords' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordWeakPassword
    $Stats.'Weak Passwords Enabled' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordWeakPasswordEnabled
    $Stats.'Weak Passwords Disabled' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordWeakPasswordDisabled
    if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.ActiveDirectory]::DomainPasswordHashesWeakPassword)) {
        $Stats.'Weak Passwords (HASH)' = Get-ObjectCount -Object $DomainPasswordHashesWeakPassword
    }
    if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.ActiveDirectory]::DomainPasswordHashesWeakPasswordEnabled)) {
        $Stats.'Weak Passwords (HASH) Enabled' = Get-ObjectCount -Object $DomainPasswordHashesWeakPasswordEnabled
    }
    if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.ActiveDirectory]::DomainPasswordHashesWeakPasswordDisabled)) {
        $Stats.'Weak Passwords (HASH) Disabled' = Get-ObjectCount -Object $DomainPasswordHashesWeakPasswordDisabled
    }
    $Stats.'Default Computer Passwords' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordDefaultComputerPassword
    $Stats.'Password Not Required' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordPasswordNotRequired
    $Stats.'Password Never Expires' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordPasswordNeverExpires
    $Stats.'AES Keys Missing' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordAESKeysMissing
    $Stats.'PreAuth Not Required' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordPreAuthNotRequired
    $Stats.'DES Encryption Only' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordDESEncryptionOnly
    $Stats.'Delegatable Admins' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordDelegatableAdmins
    $Stats.'Duplicate Password Users' = Get-ObjectCount -Object $PasswordsQuality.DomainPasswordDuplicatePasswordGroups
    $Stats.'Duplicate Password Grouped' = Get-ObjectCount ($PasswordsQuality.DomainPasswordDuplicatePasswordGroups.'Duplicate Group' | Sort-Object -Unique)
    return $Stats

}
function Get-WinADDomainRIDs {
    [CmdletBinding()]
    param(
        [Microsoft.ActiveDirectory.Management.ADDomain] $DomainInformation,
        [string] $Domain = $Env:USERDNSDOMAIN
    )
    # Critical for RID Pool Depletion: https://blogs.technet.microsoft.com/askds/2011/09/12/managing-rid-pool-depletion/
    #$Time = Start-TimeLog
    #rite-Verbose "Getting domain information - $Domain DomainRIDs"

    if ($null -eq $DomainInformation) {
        $DomainInformation = Get-ADDomain -Server $Domain
    }
    $rID = [ordered] @{ }
    $rID.'rIDs Master' = $DomainInformation.RIDMaster

    $Property = Get-ADObject "cn=rid manager$,cn=system,$($DomainInformation.DistinguishedName)" -Property RidAvailablePool -Server $rID.'rIDs Master'
    [int32]$totalSIDS = $($Property.RidAvailablePool) / ([math]::Pow(2, 32))
    [int64]$temp64val = $totalSIDS * ([math]::Pow(2, 32))
    [int32]$currentRIDPoolCount = $($Property.RidAvailablePool) - $temp64val
    [int64]$RidsRemaining = $totalSIDS - $currentRIDPoolCount

    $Rid.'rIDs Available Pool' = $Property.RidAvailablePool
    $rID.'rIDs Total SIDs' = $totalSIDS
    $rID.'rIDs Issued' = $CurrentRIDPoolCount
    $rID.'rIDs Remaining' = $RidsRemaining
    $rID.'rIDs Percentage' = if ($RidsRemaining -eq 0) { $RidsRemaining.ToString("P") } else { ($currentRIDPoolCount / $RidsRemaining * 100).ToString("P") }

    #$EndTime = Stop-TimeLog -Time $Time -Option OneLiner
    #Write-Verbose "Getting domain information - $Domain DomainRIDs Time: $EndTime"
    return $rID
}
function Get-WinADDomainServersCount {
    [CmdletBinding()]
    param(
        [Array] $DomainServers
    )
    $DomainServers | Group-Object -Property OperatingSystem | Select-Object @{ L = 'System Name'; Expression = { if ($_.Name -ne '') { $_.Name } else { 'N/A' } } } , @{ L = 'System Count'; Expression = { $_.Count } }

}
function Get-WinADDomainServers {
    [CmdletBinding()]
    param(
        [Array] $DomainComputersAll
    )
    #$DomainComputersAll | & { process { if ($_.OperatingSystem -like 'Windows Server*') { $_ } } } #| Where-Object { $_.OperatingSystem -like 'Windows Server*' }
    foreach ($_ in $DomainComputersAll) {
        if ($_.OperatingSystem -like 'Windows Server*') {
            $_
        }
    }
}
function Get-WinADDomainTrusts {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN,
        [string] $DomainPDC,
        [Array] $Trusts
    )
    if ($null -eq $Trusts) {
        $Trusts = Get-ADTrust -Server $Domain -Filter * -Properties *
    }
    if ($DomainPDC -eq '') {
        $DomainPDC = (Get-ADDomain -Server $Domain).PDCEmulator
    }

    $PropertiesTrustWMI = @(
        'FlatName',
        'SID',
        'TrustAttributes',
        'TrustDirection',
        'TrustedDCName',
        'TrustedDomain',
        'TrustIsOk',
        'TrustStatus',
        'TrustStatusString', # TrustIsOk/TrustStatus are covered by this
        'TrustType'
    )

    <# TrustWMI
        FlatName : EVOTECPL
        SID : S-1-5-21-3661168273-3802070955-2987026695
        TrustAttributes : 32
        TrustDirection : 3
        TrustedDCName : \\ADPreview2019.ad.evotec.pl
        TrustedDomain : ad.evotec.pl
        TrustIsOk : True
        TrustStatus : 0
        TrustStatusString : OK
        TrustType : 2
        PSComputerName : ad1.ad.evotec.xyz
    #>


    $TrustStatatuses = Get-CimInstance -ClassName Microsoft_DomainTrustStatus -Namespace root\MicrosoftActiveDirectory -ComputerName $DomainPDC -ErrorAction SilentlyContinue -Verbose:$false -Property $PropertiesTrustWMI

    $ReturnData = foreach ($Trust in $Trusts) {
        $TrustWMI = $TrustStatatuses | & { process { if ($_.TrustedDomain -eq $Trust.Target ) { $_ } } }
        [PsCustomObject] @{
            'Trust Source'               = $Domain
            'Trust Target'               = $Trust.Target
            'Trust Direction'            = $Trust.Direction
            'Trust Attributes'           = if ($Trust.TrustAttributes -is [int]) { Set-TrustAttributes -Value $Trust.TrustAttributes } else { 'Error - needs fixing' }
            'Trust Status'               = if ($null -ne $TrustWMI) { $TrustWMI.TrustStatusString } else { 'N/A' }
            'Forest Transitive'          = $Trust.ForestTransitive
            'Selective Authentication'   = $Trust.SelectiveAuthentication
            'SID Filtering Forest Aware' = $Trust.SIDFilteringForestAware
            'SID Filtering Quarantined'  = $Trust.SIDFilteringQuarantined
            'Disallow Transivity'        = $Trust.DisallowTransivity
            'Intra Forest'               = $Trust.IntraForest
            'Tree Parent?'               = $Trust.IsTreeParent
            'Tree Root?'                 = $Trust.IsTreeRoot
            'TGTDelegation'              = $Trust.TGTDelegation
            'TrustedPolicy'              = $Trust.TrustedPolicy
            'TrustingPolicy'             = $Trust.TrustingPolicy
            'TrustType'                  = $Trust.TrustType
            'UplevelOnly'                = $Trust.UplevelOnly
            'UsesAESKeys'                = $Trust.UsesAESKeys
            'UsesRC4Encryption'          = $Trust.UsesRC4Encryption
            'Trust Source DC'            = if ($null -ne $TrustWMI) { $TrustWMI.PSComputerName } else { '' }
            'Trust Target DC'            = if ($null -ne $TrustWMI) { $TrustWMI.TrustedDCName.Replace('\\', '') } else { '' }
            'Trust Source DN'            = $Trust.Source
            'ObjectGUID'                 = $Trust.ObjectGUID
            'Created'                    = $Trust.Created
            'Modified'                   = $Trust.Modified
            'Deleted'                    = $Trust.Deleted
            'SID'                        = $Trust.securityIdentifier
            'TrustOK'                    = if ($null -ne $TrustWMI) { $TrustWMI.TrustIsOK } else { $false }
            'TrustStatus'                = if ($null -ne $TrustWMI) { $TrustWMI.TrustStatus } else { -1 }
        }
    }

    #$EndTime = Stop-TimeLog -Time $Time -Option OneLiner
    #Write-Verbose "Getting domain information - $Domain DomainTrusts Time: $EndTime"

    return $ReturnData

}
function Get-WinADDomainTrustsClean {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN
    )
    Get-ADTrust -Server $Domain -Filter * -Properties * -ErrorAction SilentlyContinue
}
function Get-WinADDomainUsersAll {
    [CmdletBinding()]
    param(
        [Array] $Users,
        [string] $Domain = $Env:USERDNSDOMAIN,
        [System.Collections.IDictionary] $DomainObjects,
        [System.Collections.IDictionary] $DomainObjectsNetbios,
        [Object] $Domaininformation,
        [string] $Splitter
    )
    [DateTime] $CurrentDate = Get-Date
    foreach ($U in $Users) {
        $Manager = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $U.Manager
        $User = [PsCustomObject] @{
            'Name'                              = $U.Name
            'UserPrincipalName'                 = $U.UserPrincipalName
            'SamAccountName'                    = $U.SamAccountName
            'DisplayName'                       = $U.DisplayName
            'GivenName'                         = $U.GivenName
            'Surname'                           = $U.Surname
            'EmailAddress'                      = $U.EmailAddress
            'PasswordExpired'                   = $U.PasswordExpired
            'PasswordLastSet'                   = $U.PasswordLastSet
            'PasswordLastChanged(Days)'         = if ($null -ne $U.PasswordLastSet) { "$(-$($U.PasswordLastSet - $CurrentDate).Days)" } else { }
            'PasswordNotRequired'               = $U.PasswordNotRequired
            'PasswordNeverExpires'              = $U.PasswordNeverExpires
            'Enabled'                           = $U.Enabled
            'Manager'                           = $Manager.Name
            'ManagerEmail'                      = if ($Splitter -ne '') { $Manager.EmailAddress -join $Splitter } else { $Manager.EmailAddress }
            'DateExpiry'                        = Convert-ToDateTime -Timestring $($U."msDS-UserPasswordExpiryTimeComputed")
            "DaysToExpire"                      = (Convert-TimeToDays -StartTime $CurrentDate -EndTime (Convert-ToDateTime -Timestring $($U."msDS-UserPasswordExpiryTimeComputed")))
            "AccountExpirationDate"             = $U.AccountExpirationDate
            "AccountLockoutTime"                = $U.AccountLockoutTime
            "AllowReversiblePasswordEncryption" = $U.AllowReversiblePasswordEncryption
            "BadLogonCount"                     = $U.BadLogonCount
            "CannotChangePassword"              = $U.CannotChangePassword
            "CanonicalName"                     = $U.CanonicalName

            "Description"                       = $U.Description
            "DistinguishedName"                 = $U.DistinguishedName
            "EmployeeID"                        = $U.EmployeeID
            "EmployeeNumber"                    = $U.EmployeeNumber
            "LastBadPasswordAttempt"            = $U.LastBadPasswordAttempt
            "LastLogonDate"                     = $U.LastLogonDate

            "Created"                           = $U.Created
            "Modified"                          = $U.Modified
            "Protected"                         = $U.ProtectedFromAccidentalDeletion

            "Primary Group"                     = (Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $U.PrimaryGroup -Type 'SamAccountName')
            "Member Of"                         = (Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $U.MemberOf -Type 'SamAccountName' -Splitter $Splitter)
            "Domain"                            = $Domain
        }
        $Name = -join ($Domaininformation.NetBIOSName, "\", $User.SamAccountName)
        $DomainObjectsNetbios[$Name] = $User
        $User
    }
}
function Get-WinADDomainUsersAllFiltered {
    [CmdletBinding()]
    param(
        [Array] $DomainUsers
    )

    #$DomainUsers | Where-Object { $_.PasswordNotRequired -eq $False }
    #| Select-Object * #Name, SamAccountName, UserPrincipalName, Enabled
    foreach ($_ in $DomainUsers) {
        if ($_.PasswordNotRequired -eq $False) {
            $_
        }
    }

}
function Get-WinADDomainUsersExpiredExclDisabled {
    [CmdletBinding()]
    param(
        [Array] $DomainUsers
    )

    #$DomainUsers | Where-Object { $_.PasswordNeverExpires -eq $false -and $_.DaysToExpire -le 0 -and $_.Enabled -eq $true -and $_.PasswordNotRequired -eq $false } #| Select-Object * # Name, SamAccountName, UserPrincipalName, Enabled
    foreach ($_ in $DomainUsers) {
        if ($_.PasswordNeverExpires -eq $false -and $_.DaysToExpire -le 0 -and $_.Enabled -eq $true -and $_.PasswordNotRequired -eq $false) {
            $_
        }
    }
}
function Get-WinADDomainUsersExpiredInclDisabled {
    [CmdletBinding()]
    param(
        [Array] $DomainUsers
    )

    #$DomainUsers | Where-Object { $_.PasswordNeverExpires -eq $false -and $_.DaysToExpire -le 0 -and $_.PasswordNotRequired -eq $false }
    foreach ($_ in $DomainUsers) {
        if ($_.PasswordNeverExpires -eq $false -and $_.DaysToExpire -le 0 -and $_.PasswordNotRequired -eq $false) {
            $_
        }
    }
    #| Select-Object * #Name, SamAccountName, UserPrincipalName, Enabled
}
function Get-WinADDomainUsersFullList {
    [CmdletBinding()]
    param(
        [string] $Domain = $Env:USERDNSDOMAIN,
        [switch] $Extended,
        [Array] $ForestSchemaUsers,
        [System.Collections.IDictionary] $DomainObjects,
        [int] $ResultPageSize = 500000
    )
    if ($null -eq $ForestSchemaUsers) {
        $ForestSchemaUsers = @(
            $Schema = [directoryservices.activedirectory.activedirectoryschema]::GetCurrentSchema()
            @(
                $Schema.FindClass("user").MandatoryProperties #| Select-Object name, commonname, description, syntax #| export-csv user-mandatory-attributes.csv -Delimiter ';'
                $Schema.FindClass("user").OptionalProperties #| Select-Object name, commonname, description, syntax #| export-csv user-optional-attributes.csv -Delimiter ';'
                $Schema.FindClass("user").PossibleSuperiors #| Select-Object name, commonname, description, syntax
                $Schema.FindClass("user").PossibleInferiors #| Select-Object name, commonname, description, syntax
                $Schema.FindClass("user").AuxiliaryClasses
            )
            # $Schema.FindClass("user").FindAllProperties() | Select-Object name, commonname, description, syntax
        )

    }

    if ($Extended) {
        [string] $Properties = '*'
    } else {
        $Properties = @(
            'Name'
            'UserPrincipalName'
            'SamAccountName'
            'DisplayName'
            'GivenName'
            'Surname'
            'EmailAddress'
            'PasswordExpired'
            'PasswordLastSet'
            'PasswordNotRequired'
            'PasswordNeverExpires'
            'Enabled'
            'Manager'
            'msDS-UserPasswordExpiryTimeComputed'
            'AccountExpirationDate'
            'AccountLockoutTime'
            'AllowReversiblePasswordEncryption'
            'BadLogonCount'
            'CannotChangePassword'
            'CanonicalName'
            'Description'
            'DistinguishedName'
            'EmployeeID'
            'EmployeeNumber'
            'LastBadPasswordAttempt'
            'LastLogonDate'
            'Created'
            'Modified'
            'ProtectedFromAccidentalDeletion'
            'PrimaryGroup'
            'MemberOf'
            if ($ForestSchemaUsers.Name -contains 'ExtensionAttribute1') {
                'ExtensionAttribute1'
                'ExtensionAttribute2'
                'ExtensionAttribute3'
                'ExtensionAttribute4'
                'ExtensionAttribute5'
                'ExtensionAttribute6'
                'ExtensionAttribute7'
                'ExtensionAttribute8'
                'ExtensionAttribute9'
                'ExtensionAttribute10'
                'ExtensionAttribute11'
                'ExtensionAttribute12'
                'ExtensionAttribute13'
                'ExtensionAttribute14'
                'ExtensionAttribute15'
            }
        )
    }
    $Users = Get-ADUser -Server $Domain -ResultPageSize $ResultPageSize -Filter * -Properties $Properties #| Select-Object -Property $Properties -ExcludeProperty $ExcludeProperty
    if ($null -ne $DomainObjects) {
        foreach ($_ in $Users) {
            $DomainObjects[$_.DistinguishedName] = $_
        }
    }
    $Users
}
function Get-WinADDomainUsersNeverExpiring {
    [CmdletBinding()]
    param(
        [Array] $DomainUsers
    )
    #$DomainUsers | Where-Object { $_.PasswordNeverExpires -eq $true -and $_.Enabled -eq $true -and $_.PasswordNotRequired -eq $false }
    foreach ($_ in $DomainUsers) {
        if ($_.PasswordNeverExpires -eq $true -and $_.Enabled -eq $true -and $_.PasswordNotRequired -eq $false) {
            $_
        }
    }
}
function Get-WinADDomainUsersNeverExpiringInclDisabled {
    [CmdletBinding()]
    param(
        [Array] $DomainUsers
    )

    #$DomainUsers | Where-Object { $_.PasswordNeverExpires -eq $true -and $_.Enabled -eq $true -and $_.PasswordNotRequired -eq $false }
    foreach ($_ in $DomainUsers) {
        if ($_.PasswordNeverExpires -eq $true -and $_.PasswordNotRequired -eq $false) {
            $_
        }
    }
}
function Get-WinADDomainUsersSystemAccounts {
    [CmdletBinding()]
    param(
        [Array] $DomainUsers
    )

    #$DomainUsers | Where-Object { $_.PasswordNotRequired -eq $true }
    #| Select-Object * #Name, SamAccountName, UserPrincipalName, Enabled
    foreach ($_ in $DomainUsers) {
        if ($_.PasswordNotRequired -eq $true) {
            $_
        }
    }
}
function Get-WinADForest {
    [CmdletBinding()]
    param()
    # $Time = Start-TimeLog
    #Write-Verbose 'Getting forest information - Forest'
    try {
        Get-ADForest -ErrorAction Stop #| Select-Object -Property * -ExcludeProperty PropertyNames, AddedProperties, RemovedProperties, ModifiedProperties, PropertyCount
    } catch {
        $null
    }
    #$EndTime = Stop-TimeLog -Time $Time -Option OneLiner
    #Write-Verbose "Getting forest information - Forest Time: $EndTime"
}
function Get-WinADForestFSMO {
    [CmdletBinding()]
    param(
        [PSCustomObject] $Forest
    )
    [ordered] @{
        'Domain Naming Master' = $Forest.DomainNamingMaster
        'Schema Master'        = $Forest.SchemaMaster
    }
}
function Get-WinADForestInfo {
    [CmdletBinding()]
    param(
        [PSCustomObject] $Forest,
        [Microsoft.ActiveDirectory.Management.ADRootDSE] $RootDSE
    )
    #$Time = Start-TimeLog
    #Write-Verbose 'Getting forest information - Forest Information'

    [ordered] @{
        'Name'                     = $Forest.Name
        'Root Domain'              = $Forest.RootDomain
        'Forest Distingushed Name' = $RootDSE.defaultNamingContext
        'Forest Functional Level'  = $Forest.ForestMode
        'Domains Count'            = ($Forest.Domains).Count
        'Sites Count'              = ($Forest.Sites).Count
        'Domains'                  = ($Forest.Domains) -join ", "
        'Sites'                    = ($Forest.Sites) -join ", "
    }

    #$EndTime = Stop-TimeLog -Time $Time -Option OneLiner
    #Write-Verbose "Getting forest information - Forest Information Time: $EndTime"
}
function Get-WinADForestSiteLinks {
    [CmdletBinding()]
    param(

    )
    $ExludedProperties = @(
        'PropertyNames', 'AddedProperties', 'RemovedProperties', 'ModifiedProperties', 'PropertyCount'
    )
    $Properties = @(
        'Name', 'Cost', 'ReplicationFrequencyInMinutes', 'ReplInterval',
        'ReplicationSchedule', 'Created', 'Modified', 'Deleted', 'InterSiteTransportProtocol',
        'DistinguishedName', 'ProtectedFromAccidentalDeletion'
        #siteList,nTSecurityDescriptor

    )

    return Get-ADReplicationSiteLink -Filter * -Properties $Properties | Select-Object -Property $Properties -ExcludeProperty $ExludedProperties
}
function Get-WinADForestSites {
    [CmdletBinding()]
    param(
        [switch] $Formatted,
        [string] $Splitter
    )
    <#
                'nTSecurityDescriptor' = $_.'nTSecurityDescriptor'
                LastKnownParent = $_.LastKnownParent
                instanceType = $_.InstanceType
                InterSiteTopologyGenerator = $_.InterSiteTopologyGenerator
                dSCorePropagationData = $_.dSCorePropagationData
                ReplicationSchedule = $_.ReplicationSchedule.RawSchedule -join ','
                msExchServerSiteBL = $_.msExchServerSiteBL -join ','
                siteObjectBL = $_.siteObjectBL -join ','
                systemFlags = $_.systemFlags
                ObjectGUID = $_.ObjectGUID
                ObjectCategory = $_.ObjectCategory
                ObjectClass = $_.ObjectClass
                ScheduleHashingEnabled = $_.ScheduleHashingEnabled
    #>

    $DomainControllers = Get-WinADForestControllers
    $Sites = Get-ADReplicationSite -Filter * -Properties * # $Properties | Select-Object -Property $Properties -ExcludeProperty $ExludedProperties
    foreach ($Site in $Sites) {
        [Array] $DCs = $DomainControllers | Where-Object { $_.Site -eq $Site.Name }
        [Array] $Subnets = ConvertFrom-DistinguishedName -DistinguishedName $Site.'Subnets'

        if ($Formatted) {
            [PSCustomObject] @{
                'Name'                                                    = $Site.Name
                'Display Name'                                            = $Site.'DisplayName'
                'Description'                                             = $Site.'Description'
                'CanonicalName'                                           = $Site.'CanonicalName'
                'DistinguishedName'                                       = $Site.'DistinguishedName'
                'Location'                                                = $Site.'Location'
                'ManagedBy'                                               = $Site.'ManagedBy'
                'Protected From Accidental Deletion'                      = $Site.'ProtectedFromAccidentalDeletion'
                'Redundant Server Topology Enabled'                       = $Site.'RedundantServerTopologyEnabled'
                'Automatic Inter-Site Topology Generation Enabled'        = $Site.'AutomaticInterSiteTopologyGenerationEnabled'
                'Automatic Topology Generation Enabled'                   = $Site.'AutomaticTopologyGenerationEnabled'
                'Subnets'                                                 = if ($Splitter) { $Subnets -join $Splitter } else { $Subnets }
                'Subnets Count'                                           = $Subnets.Count
                'Domain Controllers'                                      = if ($Splitter) { ($DCs).HostName -join $Splitter } else { ($DCs).HostName }
                'Domain Controllers Count'                                = $DCs.Count
                'sDRightsEffective'                                       = $_.'sDRightsEffective'
                'Topology Cleanup Enabled'                                = $_.'TopologyCleanupEnabled'
                'Topology Detect Stale Enabled'                           = $_.'TopologyDetectStaleEnabled'
                'Topology Minimum Hops Enabled'                           = $_.'TopologyMinimumHopsEnabled'
                'Universal Group Caching Enabled'                         = $_.'UniversalGroupCachingEnabled'
                'Universal Group Caching Refresh Site'                    = $_.'UniversalGroupCachingRefreshSite'
                'Windows Server 2000 Bridgehead Selection Method Enabled' = $_.'WindowsServer2000BridgeheadSelectionMethodEnabled'
                'Windows Server 2000 KCC ISTG Selection Behavior Enabled' = $_.'WindowsServer2000KCCISTGSelectionBehaviorEnabled'
                'Windows Server 2003 KCC Behavior Enabled'                = $_.'WindowsServer2003KCCBehaviorEnabled'
                'Windows Server 2003 KCC Ignore Schedule Enabled'         = $_.'WindowsServer2003KCCIgnoreScheduleEnabled'
                'Windows Server 2003 KCC SiteLink Bridging Enabled'       = $_.'WindowsServer2003KCCSiteLinkBridgingEnabled'
                'Created'                                                 = $Site.Created
                'Modified'                                                = $Site.Modified
                'Deleted'                                                 = $Site.Deleted
            }
        } else {
            [PSCustomObject] @{
                'Name'                                              = $Site.Name
                'DisplayName'                                       = $Site.'DisplayName'
                'Description'                                       = $Site.'Description'
                'CanonicalName'                                     = $Site.'CanonicalName'
                'DistinguishedName'                                 = $Site.'DistinguishedName'
                'Location'                                          = $Site.'Location'
                'ManagedBy'                                         = $Site.'ManagedBy'
                'ProtectedFromAccidentalDeletion'                   = $Site.'ProtectedFromAccidentalDeletion'
                'RedundantServerTopologyEnabled'                    = $Site.'RedundantServerTopologyEnabled'
                'AutomaticInterSiteTopologyGenerationEnabled'       = $Site.'AutomaticInterSiteTopologyGenerationEnabled'
                'AutomaticTopologyGenerationEnabled'                = $Site.'AutomaticTopologyGenerationEnabled'
                'Subnets'                                           = if ($Splitter) { $Subnets -join $Splitter } else { $Subnets }
                'SubnetsCount'                                      = $Subnets.Count
                'DomainControllers'                                 = if ($Splitter) { ($DCs).HostName -join $Splitter } else { ($DCs).HostName }
                'DomainControllersCount'                            = $DCs.Count
                'sDRightsEffective'                                 = $_.'sDRightsEffective'
                'TopologyCleanupEnabled'                            = $_.'TopologyCleanupEnabled'
                'TopologyDetectStaleEnabled'                        = $_.'TopologyDetectStaleEnabled'
                'TopologyMinimumHopsEnabled'                        = $_.'TopologyMinimumHopsEnabled'
                'UniversalGroupCachingEnabled'                      = $_.'UniversalGroupCachingEnabled'
                'UniversalGroupCachingRefreshSite'                  = $_.'UniversalGroupCachingRefreshSite'
                'WindowsServer2000BridgeheadSelectionMethodEnabled' = $_.'WindowsServer2000BridgeheadSelectionMethodEnabled'
                'WindowsServer2000KCCISTGSelectionBehaviorEnabled'  = $_.'WindowsServer2000KCCISTGSelectionBehaviorEnabled'
                'WindowsServer2003KCCBehaviorEnabled'               = $_.'WindowsServer2003KCCBehaviorEnabled'
                'WindowsServer2003KCCIgnoreScheduleEnabled'         = $_.'WindowsServer2003KCCIgnoreScheduleEnabled'
                'WindowsServer2003KCCSiteLinkBridgingEnabled'       = $_.'WindowsServer2003KCCSiteLinkBridgingEnabled'
                'Created'                                           = $Site.Created
                'Modified'                                          = $Site.Modified
                'Deleted'                                           = $Site.Deleted
            }
        }
    }
}
function Get-WinADForestSPNSuffixes {
    [CmdletBinding()]
    param(
        [PSCustomObject] $Forest
    )
    @(
        #[PSCustomObject] @{
        #Name = $Forest.RootDomain
        #Type = 'Primary / Default SPN'
        #}
        foreach ($SPN in $Forest.SPNSuffixes) {
            [PSCustomObject] @{
                Name = $SPN
                #Type = 'Secondary'
            }
        }
    )
}
function Get-WinADForestSubnets {
    [CmdletBinding()]
    param(

    )
    $ExludedProperties = @(
        'PropertyNames', 'AddedProperties', 'RemovedProperties', 'ModifiedProperties', 'PropertyCount'
    )
    $Properties = @(
        'Name', 'DisplayName', 'Description', 'Site', 'ProtectedFromAccidentalDeletion', 'Created', 'Modified', 'Deleted'
    )

    return Get-ADReplicationSubnet -Filter * -Properties $Properties | Select-Object -Property $Properties -ExcludeProperty $ExludedProperties
}
function Get-WinADForestSubnets1 {
    [CmdletBinding()]
    param(
        [Array] $ForestSubnets
    )
    foreach ($Subnets in $ForestSubnets) {
        [PsCustomObject] @{
            'Name'        = $Subnets.Name
            'Description' = $Subnets.Description
            'Protected'   = $Subnets.ProtectedFromAccidentalDeletion
            'Modified'    = $Subnets.Modified
            'Created'     = $Subnets.Created
            'Deleted'     = $Subnets.Deleted
        }
    }
}
function Get-WinADForestSubnets2 {
    param(
        [Array] $ForestSubnets
    )
    @(
        foreach ($Subnets in $ForestSubnets) {
            [PsCustomObject] @{
                'Name' = $Subnets.Name
                'Site' = $Subnets.Site
            }
        }
    )
}
function Get-WinADForestUPNSuffixes {
    param(
        [PSCustomObject] $Forest
    )
    @(
        [PSCustomObject] @{
            Name = $Forest.RootDomain
            Type = 'Primary / Default UPN'
        }
        foreach ($UPN in $Forest.UPNSuffixes) {
            [PSCustomObject] @{
                Name = $UPN
                Type = 'Secondary'
            }
        }
    )
}
function Get-WinADKerberosUnconstrainedDelegation {
    param(

    )
    Get-ADObject -Filter { (UserAccountControl -BAND 0x0080000) -OR (UserAccountControl -BAND 0x1000000) -OR (msDS-AllowedToDelegateTo -like '*') } `
        -Properties Name, ObjectClass, PrimaryGroupID, UserAccountControl, ServicePrincipalName, msDS-AllowedToDelegateTo
}
function Get-WinADRootDSE {
    [CmdletBinding()]
    param(
        [string] $Domain = ($Env:USERDNSDOMAIN).ToLower()
    )
    try {
        if ($Domain -ne '') {
            Get-ADRootDSE -Properties * -Server $Domain
        } else {
            Get-ADRootDSE -Properties *
        }
    } catch {
        Write-Warning "Getting forest/domain information - $Domain RootDSE Error: $($_.Error)"
    }
}
function Get-WinGroupMembers {
    [CmdletBinding()]
    param(
        [Array] $Groups,
        [string] $Domain = $Env:USERDNSDOMAIN,
        #[System.Object[]] $ADCatalog,
        #[System.Object[]] $ADCatalogUsers,
        [ValidateSet("Recursive", "Standard")][String] $Option,
        [hashtable] $DomainObjects,
        [string] $Splitter
    )
    [DateTime] $CurrentDate = Get-Date
    if ($Option -eq 'Recursive') {
        [Array] $GroupMembersRecursive = foreach ($Group in $Groups) {
            try {
                $GroupMembership = Get-ADGroupMember -Server $Domain -Identity $Group.'Group SID' -Recursive -ErrorAction Stop
            } catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                Write-Warning "Couldn't get information about group $($Group.Name) with SID $($Group.'Group SID') error: $ErrorMessage"
                continue
            }
            foreach ($Member in $GroupMembership) {
                # $Object = (Get-ADObjectFromDistingusishedName -ADCatalog $ADCatalog -DistinguishedName $Member.DistinguishedName)
                $Object = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName  $Member.DistinguishedName
                $Manager = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $Object.Manager

                [PsCustomObject] @{
                    'Group Name'                        = $Group.'Group Name'
                    'Group SID'                         = $Group.'Group SID'
                    'Group Category'                    = $Group.'Group Category'
                    'Group Scope'                       = $Group.'Group Scope'
                    'High Privileged Group'             = if ($Group.adminCount -eq 1) { $True } else { $False }
                    'Display Name'                      = $Object.DisplayName
                    'Name'                              = $Member.Name
                    'User Principal Name'               = $Object.UserPrincipalName
                    'Sam Account Name'                  = $Object.SamAccountName
                    'Email Address'                     = $Object.EmailAddress
                    'PasswordExpired'                   = $Object.PasswordExpired
                    'PasswordLastSet'                   = $Object.PasswordLastSet
                    'PasswordNotRequired'               = $Object.PasswordNotRequired
                    'PasswordNeverExpires'              = $Object.PasswordNeverExpires
                    'Enabled'                           = $Object.Enabled
                    'SID'                               = $Member.SID.Value
                    #'Manager' = (Get-ADObjectFromDistingusishedName -ADCatalog $ADCatalogUsers -DistinguishedName $Object.Manager).Name
                    #'ManagerEmail' = (Get-ADObjectFromDistingusishedName -ADCatalog $ADCatalogUsers -DistinguishedName $Object.Manager).EmailAddress
                    'Manager'                           = $Manager.Name
                    'ManagerEmail'                      = if ($Splitter -ne '') { $Manager.EmailAddress -join $Splitter } else { $Manager.EmailAddress }

                    'DateExpiry'                        = Convert-ToDateTime -Timestring $($Object."msDS-UserPasswordExpiryTimeComputed") # -Verbose
                    "DaysToExpire"                      = (Convert-TimeToDays -StartTime $CurrentDate -EndTime (Convert-ToDateTime -Timestring $($Object."msDS-UserPasswordExpiryTimeComputed")))
                    "AccountExpirationDate"             = $Object.AccountExpirationDate
                    "AccountLockoutTime"                = $Object.AccountLockoutTime
                    "AllowReversiblePasswordEncryption" = $Object.AllowReversiblePasswordEncryption
                    "BadLogonCount"                     = $Object.BadLogonCount
                    "CannotChangePassword"              = $Object.CannotChangePassword
                    "CanonicalName"                     = $Object.CanonicalName

                    'Given Name'                        = $Object.GivenName
                    'Surname'                           = $Object.Surname

                    "Description"                       = $Object.Description
                    "DistinguishedName"                 = $Object.DistinguishedName
                    "EmployeeID"                        = $Object.EmployeeID
                    "EmployeeNumber"                    = $Object.EmployeeNumber
                    "LastBadPasswordAttempt"            = $Object.LastBadPasswordAttempt
                    "LastLogonDate"                     = $Object.LastLogonDate

                    "Created"                           = $Object.Created
                    "Modified"                          = $Object.Modified
                    "Protected"                         = $Object.ProtectedFromAccidentalDeletion
                    "Domain"                            = $Domain
                }
                # $Member
            }
        }
        if ($GroupMembersRecursive.Count -eq 1) {
            return , $GroupMembersRecursive
        }
        return $GroupMembersRecursive
    }
    if ($Option -eq 'Standard') {
        [Array] $GroupMembersDirect = foreach ($Group in $Groups) {
            foreach ($Member in $Group.'Group Members DN') {
                $Object = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName  $Member
                $Manager = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $Object.Manager

                [PsCustomObject] @{
                    'Group Name'                        = $Group.'Group Name'
                    'Group SID'                         = $Group.'Group SID'
                    'Group Category'                    = $Group.'Group Category'
                    'Group Scope'                       = $Group.'Group Scope'
                    'DisplayName'                       = $Object.DisplayName
                    'High Privileged Group'             = if ($Group.adminCount -eq 1) { $True } else { $False }
                    'UserPrincipalName'                 = $Object.UserPrincipalName
                    'SamAccountName'                    = $Object.SamAccountName
                    'EmailAddress'                      = $Object.EmailAddress
                    'PasswordExpired'                   = $Object.PasswordExpired
                    'PasswordLastSet'                   = $Object.PasswordLastSet
                    'PasswordNotRequired'               = $Object.PasswordNotRequired
                    'PasswordNeverExpires'              = $Object.PasswordNeverExpires
                    'Enabled'                           = $Object.Enabled
                    # 'Manager' = (Get-ADObjectFromDistingusishedName -ADCatalog $ADCatalogUsers -DistinguishedName $Object.Manager).Name
                    # 'ManagerEmail' = (Get-ADObjectFromDistingusishedName -ADCatalog $ADCatalogUsers -DistinguishedName $Object.Manager).EmailAddress

                    'Manager'                           = $Manager.Name
                    'ManagerEmail'                      = if ($Splitter -ne '') { $Manager.EmailAddress -join $Splitter } else { $Manager.EmailAddress }
                    'DateExpiry'                        = Convert-ToDateTime -Timestring $($Object."msDS-UserPasswordExpiryTimeComputed") #-Verbose
                    "DaysToExpire"                      = (Convert-TimeToDays -StartTime $CurrentDate -EndTime (Convert-ToDateTime -Timestring $($Object."msDS-UserPasswordExpiryTimeComputed")))
                    "AccountExpirationDate"             = $Object.AccountExpirationDate
                    "AccountLockoutTime"                = $Object.AccountLockoutTime
                    "AllowReversiblePasswordEncryption" = $Object.AllowReversiblePasswordEncryption
                    "BadLogonCount"                     = $Object.BadLogonCount
                    "CannotChangePassword"              = $Object.CannotChangePassword
                    "CanonicalName"                     = $Object.CanonicalName

                    "Description"                       = $Object.Description
                    "DistinguishedName"                 = $Object.DistinguishedName
                    "EmployeeID"                        = $Object.EmployeeID
                    "EmployeeNumber"                    = $Object.EmployeeNumber
                    "LastBadPasswordAttempt"            = $Object.LastBadPasswordAttempt
                    "LastLogonDate"                     = $Object.LastLogonDate

                    'Name'                              = $Object.Name
                    'SID'                               = $Object.SID.Value
                    'GivenName'                         = $Object.GivenName
                    'Surname'                           = $Object.Surname

                    "Created"                           = $Object.Created
                    "Modified"                          = $Object.Modified
                    "Protected"                         = $Object.ProtectedFromAccidentalDeletion
                    "Domain"                            = $Domain
                }
            }
        }
        if ($GroupMembersDirect.Count -eq 1) {
            return , $GroupMembersDirect
        }
        return $GroupMembersDirect
    }
}
function Get-WinGroups {
    [CmdletBinding()]
    param (
        [Array] $Groups,
        # [System.Object[]] $Users,
        [string] $Domain = $Env:USERDNSDOMAIN,
        [string] $Splitter,
        [hashtable] $DomainObjects
    )
    $ReturnGroups = foreach ($Group in $Groups) {
        #$User = $Users | & { process { if ($_.DistinguishedName -eq $Group.ManagedBy ) { $_ } } } # | Where-Object { $_.DistinguishedName -eq $Group.ManagedBy }
        #$User = foreach ($_ in $Users) {
        # if ($_.DistinguishedName -eq $Group.ManagedBy) { $_ }
        #}
        $Manager = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $Group.ManagedBy
        # $GroupMembers = (Get-ADObjectFromDistingusishedName -Splitter $Splitter -ADCatalog $Data.DomainUsersFullList, $Data.DomainComputersFullList, $Data.DomainGroupsFullList -DistinguishedName $Group.Members -Type 'SamAccountName')
        #$GroupMembers = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $Group.Members -Splitter $Splitter -Type 'SamAccountName'

        [PsCustomObject] @{
            'Group Name'            = [string] $Group.Name
            #'Group Display Name' = $Group.DisplayName
            'Group Category'        = [string] $Group.GroupCategory
            'Group Scope'           = [string] $Group.GroupScope
            'Group SID'             = [string] $Group.SID.Value
            'High Privileged Group' = if ($Group.adminCount -eq 1) { $True } else { $False }
            'Member Count'          = $Group.Members.Count
            'MemberOf Count'        = $Group.MemberOf.Count
            'Manager'               = $Manager.Name
            'Manager Email'         = if ($Splitter -ne '') { $Manager.EmailAddress -join $Splitter } else { $Manager.EmailAddress }
            'Group Members'         = Get-ADObjectFromDNHash -ADCatalog $DomainObjects -DistinguishedName $Group.Members -Splitter $Splitter -Type 'SamAccountName'
            'Group Members DN'      = $Group.Members #if ($Splitter -ne '') { $Group.Members -join $Splitter } else { $Group.Members }
            "Domain"                = $Domain
        }
    }
    return $ReturnGroups
}
Function Set-TrustAttributes {
    [cmdletbinding()]
    Param(
        [parameter(Mandatory = $false, ValueFromPipeline = $True)][int32]$Value
    )
    [String[]]$TrustAttributes = @(
        Foreach ($V in $Value) {
            if ([int32]$V -band 0x00000001) { "Non Transitive" }
            if ([int32]$V -band 0x00000002) { "UpLevel" }
            if ([int32]$V -band 0x00000004) { "Quarantaine (SID Filtering enabled)" } #SID Filtering
            if ([int32]$V -band 0x00000008) { "Forest Transitive" }
            if ([int32]$V -band 0x00000010) { "Cross Organization (Selective Authentication enabled)" } #Selective Auth
            if ([int32]$V -band 0x00000020) { "Within Forest" }
            if ([int32]$V -band 0x00000040) { "Treat as External" }
            if ([int32]$V -band 0x00000080) { "Uses RC4 Encryption" }
        }
    )
    return $TrustAttributes
}
function Get-WinADDomainInformation {
    [CmdletBinding()]
    param (
        [string] $Domain,
        [PSWinDocumentation.ActiveDirectory[]] $TypesRequired,
        [string] $PathToPasswords,
        [string] $PathToPasswordsHashes,
        [switch] $Extended,
        [switch] $Formatted,
        [Array] $ForestSchemaComputers,
        [Array] $ForestSchemaUsers,
        [switch] $PasswordQuality,
        [alias('Joiner')][string] $Splitter,
        [switch] $Parallel,
        [int] $ResultPageSize = 500000
    )
    # temporary set here, will be moved to variables when all functions will support it
    $Formatted = $true
    $PSDefaultParameterValues["Get-DataInformation:Verbose"] = $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent

    $Data = [ordered] @{ }

    if ($Domain -eq '') {
        Write-Warning 'Get-WinADDomainInformation - $Domain parameter is empty. Try your domain name like ad.evotec.xyz. Skipping for now...'
        return
    }
    if ($null -eq $TypesRequired) {
        Write-Verbose 'Get-WinADDomainInformation - TypesRequired is null. Getting all.'
        $TypesRequired = Get-Types -Types ([PSWinDocumentation.ActiveDirectory])
    } # Gets all types
    $TimeToGenerate = Start-TimeLog

    # this is required to make sure certain properties are used in domain, such as LAPS, EXCHANGE and so on.
    # this prevents errors of asking for wrong property - normally that would be provided by forest
    if ($null -eq $ForestSchemaComputers) {
        $ForestSchemaComputers = Get-DataInformation -Text "Getting domain information - ForestSchemaPropertiesComputers" {
            Get-WinADForestSchemaProperties -Schema 'Computers'
        } -TypesRequired $TypesRequired -TypesNeeded @(
            [PSWinDocumentation.ActiveDirectory]::ForestSchemaPropertiesComputers
            [PSWinDocumentation.ActiveDirectory]::DomainComputersFullList
            [PSWinDocumentation.ActiveDirectory]::DomainComputersAll
            [PSWinDocumentation.ActiveDirectory]::DomainComputersAllCount
            [PSWinDocumentation.ActiveDirectory]::DomainServers
            [PSWinDocumentation.ActiveDirectory]::DomainServersCount
            [PSWinDocumentation.ActiveDirectory]::DomainComputers
            [PSWinDocumentation.ActiveDirectory]::DomainComputersCount
            [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknown
            [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknownCount
            [PSWinDocumentation.ActiveDirectory]::DomainBitlocker
            [PSWinDocumentation.ActiveDirectory]::DomainLAPS
        )
    }
    if ($null -eq $ForestSchemaUsers) {
        $ForestSchemaUsers = Get-DataInformation -Text "Getting domain information - ForestSchemaPropertiesUsers" {
            Get-WinADForestSchemaProperties -Schema 'Users'
        } -TypesRequired $TypesRequired -TypesNeeded @(
            [PSWinDocumentation.ActiveDirectory]::ForestSchemaPropertiesUsers
            [PSWinDocumentation.ActiveDirectory]::DomainUsersFullList
        )
    }

    # This is standard cache
    $Data.DomainObjects = @{ }
    # this is cache by netbios name such as EVOTEC\SamAccountName
    $Data.DomainObjectsNetBios = @{}

    # Domain Root DSE - Complete TypesNeeded
    $Data.DomainRootDSE = Get-DataInformation -Text "Getting domain information - $Domain DomainRootDSE" {
        Get-WinADRootDSE -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainRootDSE
        [PSWinDocumentation.ActiveDirectory]::DomainGUIDS
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsBasicACL
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsExtendedACL
    )

    $Data.DomainInformation = Get-DataInformation -Text "Getting domain information - $Domain DomainInformation" {
        Get-WinADDomain -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainInformation
        [PSWinDocumentation.ActiveDirectory]::DomainRIDs
        [PSWinDocumentation.ActiveDirectory]::DomainFSMO
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsDN
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsBasicACL
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsExtendedACL
        [PSWinDocumentation.ActiveDirectory]::DomainAdministrators
        [PSWinDocumentation.ActiveDirectory]::DomainAdministratorsRecursive
        [PSWinDocumentation.ActiveDirectory]::DomainEnterpriseAdministrators
        [PSWinDocumentation.ActiveDirectory]::DomainEnterpriseAdministratorsRecursive
        [PSWinDocumentation.ActiveDirectory].GetEnumValues() | Where-Object { $_ -like 'DomainPassword*' }
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsPriviliged
    )

    # Groups
    $Data.DomainGroupsFullList = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupsFullList" {
        Get-WinADDomainGroupsFullList -Domain $Domain -DomainObjects $Data.DomainObjects -ResultPageSize $ResultPageSize
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsFullList
        [PSWinDocumentation.ActiveDirectory]::DomainUsers
        [PSWinDocumentation.ActiveDirectory]::DomainFineGrainedPoliciesUsers
        [PSWinDocumentation.ActiveDirectory]::DomainFineGrainedPoliciesUsersExtended
        [PSWinDocumentation.ActiveDirectory]::DomainGroups
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsPriviliged
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsMembers
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsMembersRecursive
    )


    # Users
    $Data.DomainUsersFullList = Get-DataInformation -Text "Getting domain information - $Domain DomainUsersFullList" {
        Get-WinADDomainUsersFullList -Domain $Domain -Extended:$Extended -ForestSchemaUsers $ForestSchemaUsers -DomainObjects $Data.DomainObjects -ResultPageSize $ResultPageSize
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainUsersFullList
        [PSWinDocumentation.ActiveDirectory]::DomainUsers
        [PSWinDocumentation.ActiveDirectory]::DomainUsersAll
        [PSWinDocumentation.ActiveDirectory]::DomainUsersSystemAccounts
        [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiring
        [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiringInclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersExpiredInclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersExpiredExclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersCount
        [PSWinDocumentation.ActiveDirectory]::DomainFineGrainedPoliciesUsers
        [PSWinDocumentation.ActiveDirectory]::DomainFineGrainedPoliciesUsersExtended
        [PSWinDocumentation.ActiveDirectory]::DomainGroups
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsMembers
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsMembersRecursive

        [PSWinDocumentation.ActiveDirectory].GetEnumValues() | Where-Object { $_ -like 'DomainPassword*' }
    )

    $Data.DomainComputersFullList = Get-DataInformation -Text "Getting domain information - $Domain DomainComputersFullList" {
        Get-WinADDomainComputersFullList -Domain $Domain -ForestSchemaComputers $ForestSchemaComputers -DomainObjects $Data.DomainObjects -ResultPageSize $ResultPageSize
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainComputersFullList
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAll
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAllCount
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAllBuildCount
        [PSWinDocumentation.ActiveDirectory]::DomainServers
        [PSWinDocumentation.ActiveDirectory]::DomainServersCount
        [PSWinDocumentation.ActiveDirectory]::DomainComputers
        [PSWinDocumentation.ActiveDirectory]::DomainComputersCount
        [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknown
        [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknownCount
        [PSWinDocumentation.ActiveDirectory]::DomainBitlocker
        [PSWinDocumentation.ActiveDirectory]::DomainLAPS

        [PSWinDocumentation.ActiveDirectory]::DomainUsers
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsMembers
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsMembersRecursive

        [PSWinDocumentation.ActiveDirectory].GetEnumValues() | Where-Object { $_ -like 'DomainPassword*' }
    )

    $Data.DomainComputersAll = Get-DataInformation -Text "Getting domain information - $Domain DomainComputersAll" {
        Get-WinADDomainComputersAll -DomainComputersFullList $Data.DomainComputersFullList -Splitter $Splitter -DomainObjects $Data.DomainObjects -DomainObjectsNetbios $Data.DomainObjectsNetBios -Domaininformation $Data.DomainInformation
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAll
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAllCount
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAllBuildCount
        [PSWinDocumentation.ActiveDirectory]::DomainServers
        [PSWinDocumentation.ActiveDirectory]::DomainServersCount
        [PSWinDocumentation.ActiveDirectory]::DomainComputers
        [PSWinDocumentation.ActiveDirectory]::DomainComputersCount
        [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknown
        [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknownCount

        [PSWinDocumentation.ActiveDirectory].GetEnumValues() | Where-Object { $_ -like 'DomainPassword*' }
    )

    $Data.DomainComputersAllCount = Get-DataInformation -Text "Getting domain information - $Domain DomainComputersAllCount" {
        Get-WinADDomainComputersAllCount -DomainComputersAll $Data.DomainComputersAll
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAllCount
    )
    $Data.DomainComputersAllBuildCount = Get-DataInformation -Text "Getting domain information - $Domain DomainComputersAllBuildCount" {
        Get-WinADDomainComputersAllBuildSummary -DomainComputers $Data.DomainComputersAll -Formatted:$Formatted
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAllBuildCount
    )

    $Data.DomainServers = Get-DataInformation -Text "Getting domain information - $Domain DomainServers" {
        Get-WinADDomainServers -DomainComputersAll $Data.DomainComputersAll
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainServers
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAll
    )

    $Data.DomainServersCount = Get-DataInformation -Text "Getting domain information - $Domain DomainServersCount" {
        Get-WinADDomainServersCount -DomainServers $Data.DomainServers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainServersCount
        [PSWinDocumentation.ActiveDirectory]::DomainServers
    )

    $Data.DomainComputers = Get-DataInformation -Text "Getting domain information - $Domain DomainComputers" {
        Get-WinADDomainComputers -DomainComputersAll $Data.DomainComputersAll
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainComputers
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAll
    )

    $Data.DomainComputersCount = Get-DataInformation -Text "Getting domain information - $Domain DomainComputersCount" {
        Get-WinADDomainComputersCount -DomainComputers $Data.DomainComputers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainComputersCount
        [PSWinDocumentation.ActiveDirectory]::DomainComputers
    )

    $Data.DomainComputersUnknown = Get-DataInformation -Text "Getting domain information - $Domain DomainComputersUnknown" {
        Get-WinADDomainComputersUnknown -DomainComputersAll $Data.DomainComputersAll
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknown
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAll
    )

    $Data.DomainComputersUnknownCount = Get-DataInformation -Text "Getting domain information - $Domain DomainComputersUnknownCount" {
        Get-WinADDomainComputersUnknownCount -DomainComputersUnknown $Data.DomainComputersUnknown
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknownCount
        [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknown
    )

    $Data.DomainRIDs = Get-DataInformation -Text "Getting domain information - $Domain DomainRIDs" {
        Get-WinADDomainRIDs -DomainInformation $Data.DomainInformation -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainRIDs
    )

    $Data.DomainGUIDS = Get-DataInformation -Text "Getting domain information - $Domain DomainGUIDS" {
        Get-WinADDomainGUIDs -RootDSE $Data.DomainRootDSE -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGUIDS
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsExtendedACL
    )

    $Data.DomainAuthenticationPolicies = Get-DataInformation -Text "Getting domain information - $Domain DomainAuthenticationPolicies" {
        Get-ADAuthenticationPolicy -Server $Domain -LDAPFilter '(name=AuthenticationPolicy*)'
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainAuthenticationPolicies
    )

    $Data.DomainAuthenticationPolicySilos = Get-DataInformation -Text "Getting domain information - $Domain DomainAuthenticationPolicySilos" {
        Get-ADAuthenticationPolicySilo -Server $Domain -Filter 'Name -like "*AuthenticationPolicySilo*"'
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainAuthenticationPolicySilos
    )

    $Data.DomainCentralAccessPolicies = Get-DataInformation -Text "Getting domain information - $Domain DomainCentralAccessPolicies" {
        Get-ADCentralAccessPolicy -Server $Domain -Filter *
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainCentralAccessPolicies
    )

    $Data.DomainCentralAccessRules = Get-DataInformation -Text "Getting domain information - $Domain DomainCentralAccessRules" {
        Get-ADCentralAccessRule -Server $Domain -Filter *
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainCentralAccessRules
    )

    $Data.DomainClaimTransformPolicies = Get-DataInformation -Text "Getting domain information - $Domain DomainClaimTransformPolicies" {
        Get-ADClaimTransformPolicy -Server $Domain -Filter *
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainClaimTransformPolicies
    )

    $Data.DomainClaimTypes = Get-DataInformation -Text "Getting domain information - $Domain DomainClaimTypes" {
        Get-ADClaimType -Server $Domain -Filter *
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainClaimTypes
    )

    # This won't be in main Data, needed for DomainDNSSrv/DomainDNSA
    $DomainDNSData = Get-DataInformation -Text "Getting domain information - $Domain DomainDNSData" {
        Get-WinADDomainDNSData -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainDNSSRV
        [PSWinDocumentation.ActiveDirectory]::DomainDNSA
    )

    $Data.DomainDNSSrv = Get-DataInformation -Text "Getting domain information - $Domain DomainDNSSrv" {
        $DomainDNSData.SRV
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainDNSSrv
    )

    $Data.DomainDNSA = Get-DataInformation -Text "Getting domain information - $Domain DomainDNSA" {
        $DomainDNSData.A
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainDNSA
    )

    $Data.DomainFSMO = Get-DataInformation -Text "Getting domain information - $Domain DomainFSMO" {
        Get-WinADDomainFSMO -Domain $Domain -DomainInformation $Data.DomainInformation
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainFSMO
        [PSWinDocumentation.ActiveDirectory]::DomainTrusts
    )

    $Data.DomainTrustsClean = Get-DataInformation -Text "Getting domain information - $Domain DomainTrustsClean" {
        Get-WinADDomainTrustsClean -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainTrustsClean
        [PSWinDocumentation.ActiveDirectory]::DomainTrusts
    )

    $Data.DomainTrusts = Get-DataInformation -Text "Getting domain information - $Domain DomainTrusts" {
        Get-WinADDomainTrusts -DomainPDC $Data.DomainFSMO.'PDC Emulator' -Trusts $Data.DomainTrustsClean -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainTrusts
    )

    $Data.DomainGroupPoliciesClean = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupPoliciesClean" {
        Get-GPO -Domain $Domain -All
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPoliciesClean
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPolicies
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPoliciesDetails
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPoliciesACL
    )

    $Data.DomainGroupPolicies = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupPolicies" {
        Get-WinADDomainGroupPolicies -GroupPolicies $Data.DomainGroupPoliciesClean -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPolicies
    )

    $Data.DomainGroupPoliciesDetails = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupPoliciesDetails" {
        Get-WinADDomainGroupPoliciesDetails -GroupPolicies $Data.DomainGroupPoliciesClean -Domain $Domain -Splitter $Splitter
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPoliciesDetails
    )

    $Data.DomainGroupPoliciesACL = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupPoliciesACL" {
        Get-GPOZaurrPermission -Forest $Forest -IncludeDomains $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPoliciesACL
    )

    $Data.DomainGroupPoliciesACLConsistency = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupPoliciesACLConsistency" {
        Get-GPOZaurrPermissionConsistency -Forest $Forest -IncludeDomains $Domain -VerifyInheritance
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPoliciesACLConsistency
    )

    $Data.DomainGroupPoliciesSysVol = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupPoliciesSysVol" {
        Get-GPOZaurrBroken -Forest $Forest -IncludeDomains $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPoliciesSysVol
    )

    $Data.DomainGroupPoliciesOwners = Get-DataInformation -Text "Gettting domain information - $Domain DomainGroupPoliciesOwners" {
        Get-GPOZaurrOwner -Forest $Forest -IncludeDomains $Domain -IncludeSysvol
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPoliciesOwners
    )

    $Data.DomainGroupPoliciesWMI = Get-DataInformation -Text "Gettting domain information - $Domain DomainGroupPoliciesWMI" {
        Get-GPOZaurrWMI -Forest $Forest -IncludeDomains $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPoliciesWMI
    )

    $Data.DomainGroupPoliciesLinksSummary = Get-DataInformation -Text "Gettting domain information - $Domain DomainGroupPoliciesLinksSummary" {
        Get-GPOZaurrLinkSummary -Report LinksSummary -Forest $Forest -IncludeDomains $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupPoliciesLinksSummary
    )

    $Data.DomainBitlocker = Get-DataInformation -Text "Getting domain information - $Domain DomainBitlocker" {
        Get-WinADDomainBitlocker -Domain $Domain -Computers $Data.DomainComputersFullList
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainBitlocker
    )

    $Data.DomainLAPS = Get-DataInformation -Text "Getting domain information - $Domain DomainLAPS" {
        Get-WinADDomainLAPS -Domain $Domain -Computers $Data.DomainComputersFullList -Splitter $Splitter
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainLAPS
    )

    $Data.DomainDefaultPasswordPolicy = Get-DataInformation -Text "Getting domain information - $Domain DomainDefaultPasswordPolicy" {
        Get-WinADDomainDefaultPasswordPolicy -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainDefaultPasswordPolicy
    )

    $Data.DomainOrganizationalUnitsClean = Get-DataInformation -Text "Getting domain information - $Domain DomainOrganizationalUnitsClean" {
        Get-ADOrganizationalUnit -Server $Domain -Properties * -Filter *
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsClean
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnits
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsDN
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsBasicACL
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsExtendedACL
    )

    $Data.DomainOrganizationalUnits = Get-DataInformation -Text "Getting domain information - $Domain DomainOrganizationalUnits" {
        Get-WinADDomainOrganizationalUnits -Domain $Domain -OrgnaizationalUnits $Data.DomainOrganizationalUnitsClean -DomainObjects $Data.DomainObjects
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnits
    )

    $Data.DomainOrganizationalUnitsDN = Get-DataInformation -Text "Getting domain information - $Domain DomainOrganizationalUnitsDN" {
        @(
            $Data.DomainInformation.DistinguishedName
            $Data.DomainOrganizationalUnitsClean.DistinguishedName
            # TODO:
            # Wth needs to fix
            $Data.DomainContainers.DistinguishedName
        )
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsDN
    )

    $Data.DomainOrganizationalUnitsBasicACL = Get-DataInformation -Text "Getting domain information - $Domain DomainOrganizationalUnitsBasicACL" {
        Get-WinADDomainOrganizationalUnitsACL  `
            -DomainOrganizationalUnitsClean $Data.DomainOrganizationalUnitsClean `
            -Domain $Domain `
            -NetBiosName $Data.DomainInformation.NetBIOSName `
            -RootDomainNamingContext $Data.DomainRootDSE.rootDomainNamingContext
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsBasicACL
    )

    $Data.DomainOrganizationalUnitsExtendedACL = Get-DataInformation -Text "Getting domain information - $Domain DomainOrganizationalUnitsExtendedACL" {
        Get-WinADDomainOrganizationalUnitsACLExtended  `
            -DomainOrganizationalUnitsClean $Data.DomainOrganizationalUnitsClean `
            -Domain $Domain `
            -NetBiosName $Data.DomainInformation.NetBIOSName `
            -RootDomainNamingContext $Data.DomainRootDSE.rootDomainNamingContext `
            -GUID $Data.DomainGUIDS
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainOrganizationalUnitsExtendedACL
    )

    <#
    $Data.DomainUsers = Get-DataInformation -Text "Getting domain information - $Domain DomainUsers" {
        Get-WinUsers `
            -Users $Data.DomainUsersFullList `
            -Domain $Domain `
            -ADCatalog $Data.DomainUsersFullList, $Data.DomainComputersFullList, $Data.DomainGroupsFullList `
            -ADCatalogUsers $Data.DomainUsersFullList
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainUsers
 
        [PSWinDocumentation.ActiveDirectory]::DomainUsersAll
        [PSWinDocumentation.ActiveDirectory]::DomainUsersSystemAccounts
        [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiring
        [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiringInclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersExpiredInclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersExpiredExclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersCount
    )
#>


    $Data.DomainUsers = Get-DataInformation -Text "Getting domain information - $Domain DomainUsers" {
        Get-WinADDomainUsersAll -Users $Data.DomainUsersFullList -Domain $Domain -DomainObjects $Data.DomainObjects -Splitter $Splitter -DomainObjectsNetbios $Data.DomainObjectsNetBios -Domaininformation $Data.DomainInformation
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainUsers

        [PSWinDocumentation.ActiveDirectory]::DomainUsersAll
        [PSWinDocumentation.ActiveDirectory]::DomainUsersSystemAccounts
        [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiring
        [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiringInclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersExpiredInclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersExpiredExclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersCount

        [PSWinDocumentation.ActiveDirectory].GetEnumValues() | Where-Object { $_ -like 'DomainPassword*' }
    )

    $Data.DomainUsersAll = Get-DataInformation -Text "Getting domain information - $Domain DomainUsersAll" {
        Get-WinADDomainUsersAllFiltered -DomainUsers $Data.DomainUsers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainUsersAll

        [PSWinDocumentation.ActiveDirectory].GetEnumValues() | Where-Object { $_ -like 'DomainPassword*' }
    )

    $Data.DomainUsersSystemAccounts = Get-DataInformation -Text "Getting domain information - $Domain DomainUsersSystemAccounts" {
        Get-WinADDomainUsersSystemAccounts -DomainUsers $Data.DomainUsers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainUsersSystemAccounts
    )

    $Data.DomainUsersNeverExpiring = Get-DataInformation -Text "Getting domain information - $Domain DomainUsersNeverExpiring" {
        Get-WinADDomainUsersNeverExpiring -DomainUsers $Data.DomainUsers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiring
    )

    $Data.DomainUsersNeverExpiringInclDisabled = Get-DataInformation -Text "Getting domain information - $Domain DomainUsersNeverExpiringInclDisabled" {
        Get-WinADDomainUsersNeverExpiringInclDisabled -DomainUsers $Data.DomainUsers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiringInclDisabled
    )

    $Data.DomainUsersExpiredInclDisabled = Get-DataInformation -Text "Getting domain information - $Domain DomainUsersExpiredInclDisabled" {
        Get-WinADDomainUsersExpiredInclDisabled -DomainUsers $Data.DomainUsers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainUsersExpiredInclDisabled
    )

    $Data.DomainUsersExpiredExclDisabled = Get-DataInformation -Text "Getting domain information - $Domain DomainUsersExpiredExclDisabled" {
        Get-WinADDomainUsersExpiredExclDisabled -DomainUsers $Data.DomainUsers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainUsersExpiredExclDisabled
    )

    $Data.DomainUsersCount = Get-DataInformation -Text "Getting domain information - $Domain DomainUsersCount" {
        Get-WinADDomainAllUsersCount `
            -DomainUsers $Data.DomainUsers `
            -DomainUsersAll $Data.DomainUsersAll `
            -DomainUsersExpiredExclDisabled $Data.DomainUsersExpiredExclDisabled `
            -DomainUsersExpiredInclDisabled $Data.DomainUsersExpiredInclDisabled `
            -DomainUsersNeverExpiring $Data.DomainUsersNeverExpiring `
            -DomainUsersNeverExpiringInclDisabled $Data.DomainUsersNeverExpiringInclDisabled `
            -DomainUsersSystemAccounts $Data.DomainUsersSystemAccounts


    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainUsersCount

        <#
        [PSWinDocumentation.ActiveDirectory]::DomainUsers
        [PSWinDocumentation.ActiveDirectory]::DomainUsersAll
        [PSWinDocumentation.ActiveDirectory]::DomainUsersSystemAccounts
        [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiring
        [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiringInclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersExpiredInclDisabled
        [PSWinDocumentation.ActiveDirectory]::DomainUsersExpiredExclDisabled
        #>

    )

    $Data.DomainControllers = Get-DataInformation -Text "Getting domain information - $Domain DomainControllers" {
        Get-WinADDomainControllersInternal -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainControllers
    )

    $Data.DomainFineGrainedPolicies = Get-DataInformation -Text "Getting domain information - $Domain DomainFineGrainedPolicies" {
        Get-WinADDomainFineGrainedPolicies -Domain $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainFineGrainedPolicies
        [PSWinDocumentation.ActiveDirectory]::DomainFineGrainedPoliciesUsers
        [PSWinDocumentation.ActiveDirectory]::DomainFineGrainedPoliciesUsersExtended
    )

    $Data.DomainFineGrainedPoliciesUsers = Get-DataInformation -Text "Getting domain information - $Domain DomainFineGrainedPoliciesUsers" {
        Get-WinADDomainFineGrainedPoliciesUsers -DomainFineGrainedPolicies $Data.DomainFineGrainedPolicies -DomainObjects $Data.DomainObjects
        #-DomainUsersFullList $Data.DomainUsersFullList `
        # -DomainGroupsFullList $Data.DomainGroupsFullList `

    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainFineGrainedPoliciesUsers
    )


    $Data.DomainFineGrainedPoliciesUsersExtended = Get-DataInformation -Text "Getting domain information - $Domain DomainFineGrainedPoliciesUsersExtended" {
        Get-WinADDomainFineGrainedPoliciesUsersExtended -DomainFineGrainedPolicies $Data.DomainFineGrainedPolicies -Domain $Domain -DomainObjects $Data.DomainObjects
        # -DomainUsersFullList $Data.DomainUsersFullList `
        # -DomainGroupsFullList $Data.DomainGroupsFullList `

    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainFineGrainedPoliciesUsersExtended
    )




    $Data.DomainGroups = Get-DataInformation -Text "Getting domain information - $Domain DomainGroups" {
        Get-WinGroups -Groups $Data.DomainGroupsFullList -Domain $Domain -Splitter $Splitter -DomainObjects $Data.DomainObjects
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroups
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsPriviliged
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsSpecial
    )

    $Data.DomainGroupsMembers = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupsMembers" {
        Get-WinGroupMembers -Groups $Data.DomainGroups `
            -Domain $Domain `
            -Option Standard `
            -DomainObjects $Data.DomainObjects
        #-ADCatalog $Data.DomainUsersFullList, $Data.DomainComputersFullList, $Data.DomainGroupsFullList `
        # -ADCatalogUsers $Data.DomainUsersFullList `

    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsMembers
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsSpecialMembers
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsPriviligedMembers
        [PSWinDocumentation.ActiveDirectory]::DomainAdministrators
        [PSWinDocumentation.ActiveDirectory]::DomainEnterpriseAdministrators
    )

    $Data.DomainGroupsMembersRecursive = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupsMembersRecursive" {
        Get-WinGroupMembers -Groups $Data.DomainGroups `
            -Domain $Domain `
            -Option Recursive `
            -DomainObjects $Data.DomainObjects
        # -ADCatalog $Data.DomainUsersFullList, $Data.DomainComputersFullList, $Data.DomainGroupsFullList `
        # -ADCatalogUsers $Data.DomainUsersFullList `

    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsMembersRecursive
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsSpecialMembersRecursive
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsPriviligedMembersRecursive
        [PSWinDocumentation.ActiveDirectory]::DomainAdministratorsRecursive
        [PSWinDocumentation.ActiveDirectory]::DomainEnterpriseAdministratorsRecursive
    )

    $Data.DomainGroupsPriviliged = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupsPriviliged" {
        Get-DomainGroupsPriviliged -DomainGroups $Data.DomainGroups -DomainInformation $Data.DomainInformation
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsPriviliged
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsPriviligedMembers
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsPriviligedMembersRecursive
    )

    $Data.DomainGroupsSpecial = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupsSpecial" {
        Get-WinADDomainGroupsSpecial -DomainGroups $Data.DomainGroups
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsSpecial
        [PSWinDocumentation.ActiveDirectory]::DomainGroupMembersRecursiveSpecial
    )

    $Data.DomainGroupsSpecialMembers = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupsSpecialMembers" {
        Get-WinADDomainGroupsSpecialMembers -DomainGroupsMembers $Data.DomainGroupsMembers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsSpecialMembers
    )

    $Data.DomainGroupsSpecialMembersRecursive = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupsSpecialMembersRecursive" {
        Get-WinADDomainGroupsSpecialMembersRecursive -DomainGroupsMembersRecursive $Data.DomainGroupsMembersRecursive
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsSpecialMembersRecursive
    )

    $Data.DomainGroupsPriviligedMembers = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupsPriviligedMembers" {
        ### NEEDS REVIEW, something looks wrong
        Get-WinADDomainGroupsPriviligedMembers -DomainGroupsMembers $Data.DomainGroupsMembers -DomainGroupsPriviliged $Data.DomainGroupsPriviliged
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsPriviligedMembers
    )
    $Data.DomainGroupsPriviligedMembersRecursive = Get-DataInformation -Text "Getting domain information - $Domain DomainGroupsPriviligedMembersRecursive" {
        ### NEEDS REVIEW, something looks wrong
        Get-WinADDomainGroupsPriviligedMembersRecursive -DomainGroupsMembersRecursive $Data.DomainGroupsMembersRecursive -DomainGroupsPriviliged $Data.DomainGroupsPriviliged
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainGroupsPriviligedMembersRecursive
    )

    $Data.DomainAdministrators = Get-DataInformation -Text "Getting domain information - $Domain DomainAdministrators" {
        Get-WinADDomainAdministrators -DomainGroupsMembers $Data.DomainGroupsMembers -DomainInformation $Data.DomainInformation
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainAdministrators
    )
    $Data.DomainAdministratorsRecursive = Get-DataInformation -Text "Getting domain information - $Domain DomainAdministratorsRecursive" {
        Get-WinADDomainAdministratorsRecursive -DomainGroupsMembersRecursive $Data.DomainGroupsMembersRecursive -DomainInformation $Data.DomainInformation
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainAdministratorsRecursive
    )


    $Data.DomainEnterpriseAdministrators = Get-DataInformation -Text "Getting domain information - $Domain DomainEnterpriseAdministrators" {
        Get-WinADDomainEnterpriseAdministrators -DomainGroupsMembers $Data.DomainGroupsMembers -DomainInformation $Data.DomainInformation
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainEnterpriseAdministrators
    )
    $Data.DomainEnterpriseAdministratorsRecursive = Get-DataInformation -Text "Getting domain information - $Domain DomainEnterpriseAdministratorsRecursive" {
        Get-WinADDomainEnterpriseAdministratorsRecursive -DomainGroupsMembersRecursive $Data.DomainGroupsMembersRecursive -DomainInformation $Data.DomainInformation
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainEnterpriseAdministratorsRecursive
    )

    $Data.DomainWellKnownFolders = Get-DataInformation -Text "Gettting domain information - $Domain DomainWellKnownFolders" {
        Get-WinADWellKnownFolders -IncludeDomains $Domain
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainWellKnownFolders
    )
    # PASSWORD QUALITY SECTION

    $Data.DomainPasswordDataUsers = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordDataUsers" {
        Get-WinADDomainPassword -DnsRoot $Data.DomainInformation.DNSRoot -DistinguishedName $Data.DomainInformation.DistinguishedName
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory].GetEnumValues() | Where-Object { $_ -like 'DomainPassword*' }
    )

    $Data.DomainPasswordDataPasswords = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordDataPasswords" {
        Get-WinADDomainPasswordQuality `
            -FilePath $PathToPasswords `
            -DomainDistinguishedName $Data.DomainInformation.DistinguishedName `
            -DnsRoot $Data.DomainInformation.DnsRoot `
            -Verbose:$false `
            -PasswordQualityUsers $Data.DomainPasswordDataUsers `
            -PasswordQuality:$PasswordQuality.IsPresent `
            -DomainObjectsNetbios $Data.DomainObjectsNetBios
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory].GetEnumValues() | Where-Object { $_ -like 'DomainPassword*' }
    )

    $Data.DomainPasswordDataPasswordsHashes = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordDataPasswordsHashes" {
        Get-WinADDomainPasswordQuality `
            -FilePath $PathToPasswordsHashes `
            -DomainDistinguishedName $Data.DomainInformation.DistinguishedName `
            -DnsRoot $DomainInformation.DnsRoot `
            -UseHashes `
            -Verbose:$false `
            -PasswordQualityUsers $Data.DomainPasswordDataUsers `
            -PasswordQuality:$PasswordQuality.IsPresent `
            -DomainObjectsNetbios $Data.DomainObjectsNetBios
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordHashesWeakPassword,
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordHashesWeakPasswordEnabled,
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordHashesWeakPasswordDisabled
    )

    if ($Data.DomainPasswordDataPasswords) {
        $PasswordsQuality = $Data.DomainPasswordDataPasswords
    } elseif ($Data.DomainPasswordDataPasswordsHashes) {
        $PasswordsQuality = $Data.DomainPasswordDataPasswordsHashes
    } else {
        $PasswordsQuality = $null
    }


    $Data.DomainPasswordClearTextPassword = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordClearTextPassword" {
        $PasswordsQuality.DomainPasswordClearTextPassword
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordClearTextPassword
    )
    $Data.DomainPasswordLMHash = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordLMHash" {
        $PasswordsQuality.DomainPasswordLMHash
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordLMHash
    )
    $Data.DomainPasswordEmptyPassword = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordEmptyPassword" {
        $PasswordsQuality.DomainPasswordEmptyPassword
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordEmptyPassword
    )
    $Data.DomainPasswordEmptyPassword = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordEmptyPassword" {
        $PasswordsQuality.DomainPasswordEmptyPassword
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordEmptyPassword
    )

    $Data.DomainPasswordWeakPassword = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordWeakPassword" {
        $PasswordsQuality.DomainPasswordWeakPassword
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordWeakPassword
    )

    $Data.DomainPasswordWeakPasswordEnabled = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordWeakPasswordEnabled" {
        $PasswordsQuality.DomainPasswordWeakPasswordEnabled
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordWeakPasswordEnabled
    )

    $Data.DomainPasswordWeakPasswordDisabled = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordWeakPasswordDisabled" {
        $PasswordsQuality.DomainPasswordWeakPasswordDisabled
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordWeakPasswordDisabled
    )

    $Data.DomainPasswordWeakPasswordList = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordWeakPasswordList" {
        $PasswordsQuality.DomainPasswordWeakPasswordList
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordWeakPasswordList
    )

    $Data.DomainPasswordDefaultComputerPassword = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordDefaultComputerPassword" {
        $PasswordsQuality.DomainPasswordDefaultComputerPassword
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordDefaultComputerPassword
    )

    $Data.DomainPasswordPasswordNotRequired = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordPasswordNotRequired" {
        $PasswordsQuality.DomainPasswordPasswordNotRequired
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordPasswordNotRequired
    )

    $Data.DomainPasswordPasswordNeverExpires = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordPasswordNeverExpires" {
        $PasswordsQuality.DomainPasswordPasswordNeverExpires
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordPasswordNeverExpires
    )

    $Data.DomainPasswordAESKeysMissing = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordAESKeysMissing" {
        $PasswordsQuality.DomainPasswordAESKeysMissing
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordAESKeysMissing
    )

    $Data.DomainPasswordPreAuthNotRequired = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordPreAuthNotRequired" {
        $PasswordsQuality.DomainPasswordPreAuthNotRequired
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordPreAuthNotRequired
    )

    $Data.DomainPasswordDESEncryptionOnly = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordDESEncryptionOnly" {
        $PasswordsQuality.DomainPasswordDESEncryptionOnly
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordDESEncryptionOnly
    )

    $Data.DomainPasswordDelegatableAdmins = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordDelegatableAdmins" {
        $PasswordsQuality.DomainPasswordDelegatableAdmins
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordDelegatableAdmins
    )

    $Data.DomainPasswordDuplicatePasswordGroups = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordDuplicatePasswordGroups" {
        $PasswordsQuality.DomainPasswordDuplicatePasswordGroups
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordDuplicatePasswordGroups
    )

    $Data.DomainPasswordHashesWeakPassword = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordHashesWeakPassword" {
        $PasswordsQuality.DomainPasswordHashesWeakPassword
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordHashesWeakPassword
    )

    $Data.DomainPasswordHashesWeakPasswordEnabled = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordHashesWeakPasswordEnabled" {
        $PasswordsQuality.DomainPasswordHashesWeakPasswordEnabled
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordHashesWeakPasswordEnabled
    )

    $Data.DomainPasswordHashesWeakPasswordDisabled = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordHashesWeakPasswordDisabled" {
        $Data.DomainPasswordDataPasswordsHashes.DomainPasswordWeakPasswordDisabled
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordHashesWeakPasswordDisabled
    )
    $Data.DomainPasswordSmartCardUsersWithPassword = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordSmartCardUsersWithPassword" {
        $Data.DomainPasswordDataPasswordsHashes.DomainPasswordSmartCardUsersWithPassword
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordSmartCardUsersWithPassword
    )
    $Data.DomainPasswordStats = Get-DataInformation -Text "Getting domain information - $Domain DomainPasswordStats" {
        Get-WinADDomainPasswordStats -PasswordsQuality $PasswordsQuality -TypesRequired $TypesRequired `
            -DomainPasswordHashesWeakPassword $Data.DomainPasswordHashesWeakPassword `
            -DomainPasswordHashesWeakPasswordEnabled $Data.DomainPasswordHashesWeakPasswordEnabled `
            -DomainPasswordHashesWeakPasswordDisabled $Data.DomainPasswordHashesWeakPasswordDisabled

    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::DomainPasswordStats
    )


    $EndTime = Stop-TimeLog -Time $TimeToGenerate
    Write-Verbose "Getting domain information - $Domain - Time to generate: $EndTime"
    return $Data
}

function Get-WinADForestInformation {
    [CmdletBinding()]
    param (
        [PSWinDocumentation.ActiveDirectory[]] $TypesRequired,
        [switch] $RequireTypes,
        [string] $PathToPasswords,
        [string] $PathToPasswordsHashes,
        [switch] $PasswordQuality,
        [switch] $DontRemoveSupportData,
        [switch] $DontRemoveEmpty,
        [switch] $Formatted,
        [string] $Splitter,
        [switch] $Parallel,
        [switch] $Extended,
        [int] $ResultPageSize = 500000
    )
    $PSDefaultParameterValues["Get-DataInformation:Verbose"] = $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent

    Write-Verbose -Message "Getting all information - Start"
    Write-Verbose -Message "Getting forest information - Start"
    $TimeToGenerateForest = Start-TimeLog
    if ($null -eq $TypesRequired) {
        # Gets all types
        Write-Verbose 'Getting forest information - TypesRequired is null. Getting all.'
        $TypesRequired = Get-Types -Types ([PSWinDocumentation.ActiveDirectory])
    }

    # Forest is required to get all domains
    $Forest = Get-WinADForest
    if ($null -eq $Forest) {
        Write-Warning "Getting forest information - Failed to get information. This may mean that RSAT is not available or you can't connect to Active Directory."
        return
    }
    # Start of building data
    $Data = [ordered] @{ }

    $Data.ForestRootDSE = Get-DataInformation -Text 'Getting forest information - ForestRootDSE' {
        Get-WinADRootDSE
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestRootDSE
        [PSWinDocumentation.ActiveDirectory]::ForestInformation
    )

    $Data.ForestInformation = Get-DataInformation -Text 'Getting forest information - Forest' {
        Get-WinADForestInfo -Forest $Forest -RootDSE $Data.ForestRootDSE
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestInformation
    )

    # This is Forest Schema Properties for Users and Computers
    $Data.ForestSchemaPropertiesComputers = Get-DataInformation -Text "Getting forest information - ForestSchemaPropertiesComputers" {
        Get-WinADForestSchemaProperties -Schema 'Computers'
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestSchemaPropertiesComputers
        [PSWinDocumentation.ActiveDirectory]::DomainComputersFullList
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAll
        [PSWinDocumentation.ActiveDirectory]::DomainComputersAllCount
        [PSWinDocumentation.ActiveDirectory]::DomainServers
        [PSWinDocumentation.ActiveDirectory]::DomainServersCount
        [PSWinDocumentation.ActiveDirectory]::DomainComputers
        [PSWinDocumentation.ActiveDirectory]::DomainComputersCount
        [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknown
        [PSWinDocumentation.ActiveDirectory]::DomainComputersUnknownCount
        [PSWinDocumentation.ActiveDirectory]::DomainBitlocker
        [PSWinDocumentation.ActiveDirectory]::DomainLAPS
        [PSWinDocumentation.ActiveDirectory]::ForestOptionalFeatures
    )
    $Data.ForestSchemaPropertiesUsers = Get-DataInformation -Text "Getting forest information - ForestSchemaPropertiesUsers" {
        Get-WinADForestSchemaProperties -Schema 'Users'
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestSchemaPropertiesUsers
        [PSWinDocumentation.ActiveDirectory]::DomainUsersFullList
    )


    ## Forest Information
    $Data.ForestUPNSuffixes = Get-DataInformation -Text 'Getting forest information - ForestUPNSuffixes' {
        Get-WinADForestUPNSuffixes -Forest $Forest
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestUPNSuffixes
    )

    $Data.ForestSPNSuffixes = Get-DataInformation -Text 'Getting forest information - ForestSPNSuffixes' {
        Get-WinADForestSPNSuffixes -Forest $Forest
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestSPNSuffixes
    )

    # Forest DC GC - Review if required
    $Data.ForestGlobalCatalogs = Get-DataInformation -Text 'Getting forest information - ForestGlobalCatalogs' {
        $Forest.GlobalCatalogs
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestGlobalCatalogs
    )

    $Data.ForestFSMO = Get-DataInformation -Text 'Getting forest information - ForestFSMO' {
        Get-WinADForestFSMO -Forest $Forest
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestFSMO
    )

    $Data.ForestDomainControllers = Get-DataInformation -Text 'Getting forest information - ForestDomainControllers' {
        # External command from PSSharedGoods
        Get-WinADForestControllers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestDomainControllers
    )

    # Forest Sites
    $Data.ForestSites = Get-DataInformation -Text 'Getting forest information - ForestSites' {
        Get-WinADForestSites -Formatted:$Formatted -Splitter $Splitter
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestSites
        [PSWinDocumentation.ActiveDirectory]::ForestSites1
        [PSWinDocumentation.ActiveDirectory]::ForestSites2
    )
    $Data.ForestSites1 = Get-DataInformation -Text 'Getting forest information - ForestSites1' {
        if ($Formatted) {
            $Data.ForestSites | Select-Object -Property Name, Description, Protected, 'Subnets Count', 'Domain Controllers Count', Modified
        } else {
            $Data.ForestSites | Select-Object -Property Name, Description, Protected, 'SubnetsCount', 'DomainControllersCount', Modified
        }
        #Get-WinADForestSites1 -ForestSites $Data.ForestSites
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestSites1
    )
    $Data.ForestSites2 = Get-DataInformation -Text 'Getting forest information - ForestSites2' {
        if ($Formatted) {
            $Data.ForestSites | Select-Object -Property 'Topology Cleanup Enabled', 'Topology DetectStale Enabled', 'Topology MinimumHops Enabled', 'Universal Group Caching Enabled', 'Universal Group Caching Refresh Site'
        } else {
            $Data.ForestSites | Select-Object -Property TopologyCleanupEnabled, TopologyDetectStaleEnabled, TopologyMinimumHopsEnabled, UniversalGroupCachingEnabled, UniversalGroupCachingRefreshSite
        }
        #Get-WinADForestSites2 -ForestSites $Data.ForestSites
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestSites2
    )

    ## Forest Subnets
    $Data.ForestSubnets = Get-DataInformation -Text 'Getting forest information - ForestSubnets' {
        Get-WinADForestSubnets
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestSubnets
        [PSWinDocumentation.ActiveDirectory]::ForestSubnets1
        [PSWinDocumentation.ActiveDirectory]::ForestSubnets2
    )
    $Data.ForestSubnets1 = Get-DataInformation -Text 'Getting forest information - ForestSubnets1' {
        Get-WinADForestSubnets1 -ForestSubnets $Data.ForestSubnets
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestSubnets1
    )
    $Data.ForestSubnets2 = Get-DataInformation -Text 'Getting forest information - ForestSubnets2' {
        Get-WinADForestSubnets2 -ForestSubnets $Data.ForestSubnets
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestSubnets2
    )

    ## Forest Site Links
    $Data.ForestSiteLinks = Get-DataInformation -Text 'Getting forest information - ForestSiteLinks' {
        Get-WinADForestSiteLinks
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestSiteLinks
    )

    ## Forest Optional Features
    $Data.ForestOptionalFeatures = Get-DataInformation -Text 'Getting forest information - ForestOptionalFeatures' {
        Get-WinADForestOptionalFeatures -ComputerProperties $ForestSchemaPropertiesComputers
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestOptionalFeatures
    )

    $Data.ForestReplication = Get-DataInformation -Text 'Getting forest information - ForestReplication' {
        Get-WinADForestReplication -Extended:$Extended
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory]::ForestReplication
    )

    $EndTimeForest = Stop-TimeLog -Time $TimeToGenerateForest -Continue

    $Data.FoundDomains = Get-DataInformation -Text 'Getting forest information - Domains' {
        ### Generate Data from Domains
        $FoundDomains = @{ }
        foreach ($Domain in $Forest.Domains) {
            $FoundDomains.$Domain = Get-WinADDomainInformation -Domain $Domain `
                -TypesRequired $TypesRequired `
                -PathToPasswords $PathToPasswords `
                -PathToPasswordsHashes $PathToPasswordsHashes `
                -ForestSchemaComputers $Data.ForestSchemaPropertiesComputers  `
                -ForestSchemaUsers $Data.ForestSchemaPropertiesUsers -PasswordQuality:$PasswordQuality.IsPresent -Splitter $Splitter -Parallel:$Parallel.IsPresent -ResultPageSize $ResultPageSize -Formatted:$formatted.IsPresent
        }
        $FoundDomains
    } -TypesRequired $TypesRequired -TypesNeeded @(
        [PSWinDocumentation.ActiveDirectory].GetEnumValues() | Where-Object { $_ -like 'Domain*' }
    )

    $EndTimeAll = Stop-TimeLog -Time $TimeToGenerateForest
    # cleans up empty fields created during gathering process
    Clear-DataInformation -Data $Data -TypesRequired $TypesRequired -DontRemoveSupportData:$DontRemoveSupportData -DontRemoveEmpty:$DontRemoveEmpty

    # final output
    Write-Verbose "Getting forest information - Stop - Time to generate: $EndTimeForest"
    Write-Verbose "Getting all information - Stop - Time to generate: $EndTimeAll"
    return $Data
}

function Invoke-ADPasswordAnalysis {
    [CmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string] $PathToPasswords,
        [switch] $UseNTLMHashes
    )
    $Output = [ordered] @{ }
    $DomainObjectsNetbios = @{}     # Cache
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $ForestSchemaComputers = Get-WinADForestSchemaProperties -Schema 'Computers' -Forest $Forest
    $ForestSchemaUsers = Get-WinADForestSchemaProperties -Schema 'Users' -Forest $Forest
    foreach ($Domain in $ForestInformation.Domains) {
        $Passwords = Get-WinADDomainPassword -DnsRoot $ForestInformation.DomainsExtended[$Domain].DNSRoot -DistinguishedName $ForestInformation.DomainsExtended[$Domain].DistinguishedName
        $Users = Get-WinADDomainUsersFullList -Domain $Domain -Extended:$Extended -ForestSchemaUsers $ForestSchemaUsers -DomainObjects $Data.DomainObjects -ResultPageSize $ResultPageSize
        $Computers = Get-WinADDomainComputersFullList -Domain $Domain -ForestSchemaComputers $ForestSchemaComputers -DomainObjects $Data.DomainObjects -ResultPageSize $ResultPageSize
        # We use null because it returns all data to DomainObjectsNetbios cache which is then used to
        $null = Get-WinADDomainUsersAll -Users $Users -DomainObjectsNetbios $DomainObjectsNetbios -DomainInformation $ForestInformation.DomainsExtended[$Domain]
        $null = Get-WinADDomainComputersAll -DomainComputersFullList $Computers -DomainObjectsNetbios $DomainObjectsNetbios -DomainInformation $ForestInformation.DomainsExtended[$Domain]
        $Quality = Get-WinADDomainPasswordQuality -DnsRoot $ForestInformation.DomainsExtended[$Domain].DnsRoot -DomainObjectsNetbios $DomainObjectsNetbios -PasswordQuality `
            -DomainDistinguishedName $ForestInformation.DomainsExtended[$Domain].DistinguishedName -PasswordQualityUsers $Passwords -FilePath $PathToPasswords -UseHashes:$UseNTLMHashes.IsPresent
        $Output["$($ForestInformation.DomainsExtended[$Domain].DnsRoot)"] = $Quality
    }
    $Output
}



Export-ModuleMember -Function @('Get-WinADDomainInformation', 'Get-WinADForestInformation', 'Invoke-ADPasswordAnalysis') -Alias @()