ADEssentials.psm1

function Convert-DomainFqdnToNetBIOS {
    <#
    .SYNOPSIS
    Converts FQDN to NetBIOS name for Active Directory Domain
 
    .DESCRIPTION
    Converts FQDN to NetBIOS name for Active Directory Domain
 
    .PARAMETER DomainName
    DomainName for current forest or trusted forest
 
    .EXAMPLE
    Convert-DomainFqdnToNetBIOS -Domain 'ad.evotec.xyz'
 
    .EXAMPLE
    Convert-DomainFqdnToNetBIOS -Domain 'ad.evotec.pl'
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param ([string] $DomainName)
    if (-not $Script:CacheFQDN) { $Script:CacheFQDN = @{} }
    if ($Script:CacheFQDN[$DomainName]) { $Script:CacheFQDN[$DomainName] } else {
        $objRootDSE = [System.DirectoryServices.DirectoryEntry] "LDAP://$DomainName/RootDSE"
        $ConfigurationNC = $objRootDSE.configurationNamingContext
        $Searcher = [System.DirectoryServices.DirectorySearcher] @{SearchScope = "subtree"
            SearchRoot                                                         = "LDAP://cn=Partitions,$ConfigurationNC"
            Filter                                                             = "(&(objectcategory=Crossref)(dnsRoot=$DomainName)(netbiosname=*))"
        }
        $null = $Searcher.PropertiesToLoad.Add("netbiosname")
        $Script:CacheFQDN[$DomainName] = ($Searcher.FindOne()).Properties.Item("netbiosname")
        $Script:CacheFQDN[$DomainName]
    }
}
function Convert-ExchangeRecipient {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER RecipientTypeDetails
    Parameter description
 
    .PARAMETER RecipientType
    Parameter description
 
    .PARAMETER RemoteRecipientType
    Parameter description
 
    .EXAMPLE
    $Users = Get-ADUser -Filter * -Properties Mail, ProxyAddresses, msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails, MailNickName
    $UsersModified = foreach ($User in $Users) {
        [PSCUstomObject] @{
            Name = $User.Name
            Mail = $User.Mail
            MailNickName = $User.MailNickName
            msExchRemoteRecipientType = Convert-ExchangeRecipient -msExchRemoteRecipientType $User.msExchRemoteRecipientType
            msExchRecipientDisplayType = Convert-ExchangeRecipient -msExchRecipientDisplayType $User.msExchRecipientDisplayType
            msExchRecipientTypeDetails = Convert-ExchangeRecipient -msExchRecipientTypeDetails $User.msExchRecipientTypeDetails
            ProxyAddresses = Convert-ExchangeEmail -AddSeparator -RemovePrefix -RemoveDuplicates -Separator ',' -Emails $User.ProxyAddresses
        }
    }
    $UsersModified | Out-HtmlView -Filtering -ScrollX
 
    .NOTES
    General notes
    #>

    [cmdletbinding(DefaultParameterSetName = 'msExchRecipientTypeDetails')]
    param([parameter(ParameterSetName = 'msExchRecipientTypeDetails')][alias('msExchRecipientTypeDetails')][string] $RecipientTypeDetails,
        [parameter(ParameterSetName = 'msExchRecipientDisplayType')][alias('msExchRecipientDisplayType')][string] $RecipientType,
        [parameter(ParameterSetName = 'msExchRemoteRecipientType')][alias('msExchRemoteRecipientType')][string] $RemoteRecipientType)
    if ($RecipientTypeDetails) {
        $msExchRecipientTypeDetails = [ordered] @{'1' = 'UserMailbox'
            '2'                                       = 'LinkedMailbox'
            '4'                                       = 'SharedMailbox'
            '16'                                      = 'RoomMailbox'
            '32'                                      = 'EquipmentMailbox'
            '128'                                     = 'MailUser'
            '2147483648'                              = 'RemoteUserMailbox'
            '8589934592'                              = 'RemoteRoomMailbox'
            '17179869184'                             = 'RemoteEquipmentMailbox'
            '34359738368'                             = 'RemoteSharedMailbox'
        }
        $msExchRecipientTypeDetails[$RecipientTypeDetails]
    } elseif ($RecipientType) {
        $msExchRecipientDisplayType = [ordered] @{'-2147483642' = 'MailUser (RemoteUserMailbox)'
            '-2147481850'                                       = 'MailUser (RemoteRoomMailbox)'
            '-2147481594'                                       = 'MailUser (RemoteEquipmentMailbox)'
            '0'                                                 = 'UserMailbox (shared)'
            '1'                                                 = 'MailUniversalDistributionGroup'
            '6'                                                 = 'MailContact'
            '7'                                                 = 'UserMailbox (room)'
            '8'                                                 = 'UserMailbox (equipment)'
            '1073741824'                                        = 'UserMailbox'
            '1073741833'                                        = 'MailUniversalSecurityGroup'
        }
        $msExchRecipientDisplayType[$RecipientType]
    } elseif ($RemoteRecipientType) {
        $msExchRemoteRecipientType = [ordered] @{'1' = 'ProvisionMailbox'
            '2'                                      = 'ProvisionArchive (On-Prem Mailbox)'
            '3'                                      = 'ProvisionMailbox, ProvisionArchive'
            '4'                                      = 'Migrated (UserMailbox)'
            '6'                                      = 'ProvisionArchive, Migrated'
            '8'                                      = 'DeprovisionMailbox'
            '10'                                     = 'ProvisionArchive, DeprovisionMailbox'
            '16'                                     = 'DeprovisionArchive (On-Prem Mailbox)'
            '17'                                     = 'ProvisionMailbox, DeprovisionArchive'
            '20'                                     = 'Migrated, DeprovisionArchive'
            '24'                                     = 'DeprovisionMailbox, DeprovisionArchive'
            '33'                                     = 'ProvisionMailbox, RoomMailbox'
            '35'                                     = 'ProvisionMailbox, ProvisionArchive, RoomMailbox'
            '36'                                     = 'Migrated, RoomMailbox'
            '38'                                     = 'ProvisionArchive, Migrated, RoomMailbox'
            '49'                                     = 'ProvisionMailbox, DeprovisionArchive, RoomMailbox'
            '52'                                     = 'Migrated, DeprovisionArchive, RoomMailbox'
            '65'                                     = 'ProvisionMailbox, EquipmentMailbox'
            '67'                                     = 'ProvisionMailbox, ProvisionArchive, EquipmentMailbox'
            '68'                                     = 'Migrated, EquipmentMailbox'
            '70'                                     = 'ProvisionArchive, Migrated, EquipmentMailbox'
            '81'                                     = 'ProvisionMailbox, DeprovisionArchive, EquipmentMailbox'
            '84'                                     = 'Migrated, DeprovisionArchive, EquipmentMailbox'
            '100'                                    = 'Migrated, SharedMailbox'
            '102'                                    = 'ProvisionArchive, Migrated, SharedMailbox'
            '116'                                    = 'Migrated, DeprovisionArchive, SharedMailbox'
        }
        $msExchRemoteRecipientType[$RemoteRecipientType]
    }
}
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 ConvertFrom-NetbiosName {
    [cmdletBinding()]
    param([Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)]
        [string[]] $Identity)
    process {
        foreach ($Ident in $Identity) {
            if ($Ident -like '*\*') {
                $NetbiosWithObject = $Ident -split "\\"
                if ($NetbiosWithObject.Count -eq 2) {
                    $LDAPQuery = ([ADSI]"LDAP://$($NetbiosWithObject[0])")
                    $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $LDAPQuery.distinguishedName -ToDomainCN
                    [PSCustomObject] @{DomainName = $DomainName
                        Name                      = $NetbiosWithObject[1]
                    }
                } else {
                    [PSCustomObject] @{DomainName = ''
                        Name                      = $Ident
                    }
                }
            } else {
                [PSCustomObject] @{DomainName = ''
                    Name                      = $Ident
                }
            }
        }
    }
}
function ConvertFrom-SID {
    <#
    .SYNOPSIS
    Small command that can resolve SID values
 
    .DESCRIPTION
    Small command that can resolve SID values
 
    .PARAMETER SID
    Value to resolve
 
    .PARAMETER OnlyWellKnown
    Only resolve SID when it's well know SID. Otherwise return $null
 
    .PARAMETER OnlyWellKnownAdministrative
    Only resolve SID when it's administrative well know SID. Otherwise return $null
 
    .PARAMETER DoNotResolve
    Uses only dicrionary values without querying AD
 
    .EXAMPLE
    ConvertFrom-SID -SID 'S-1-5-8', 'S-1-5-9', 'S-1-5-11', 'S-1-5-18', 'S-1-1-0' -DoNotResolve
 
    .NOTES
    General notes
    #>

    [cmdletbinding(DefaultParameterSetName = 'Standard')]
    param([Parameter(ParameterSetName = 'Standard')]
        [Parameter(ParameterSetName = 'OnlyWellKnown')]
        [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')]
        [string[]] $SID,
        [Parameter(ParameterSetName = 'OnlyWellKnown')][switch] $OnlyWellKnown,
        [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')][switch] $OnlyWellKnownAdministrative,
        [Parameter(ParameterSetName = 'Standard')][switch] $DoNotResolve)
    $WellKnownAdministrative = @{'S-1-5-18' = [PSCustomObject] @{Name = 'NT AUTHORITY\SYSTEM'
            SID                                                       = 'S-1-5-18'
            DomainName                                                = ''
            Type                                                      = 'WellKnownAdministrative'
            Error                                                     = ''
        }
        'S-1-5-32-544'                      = [PSCustomObject] @{Name = 'BUILTIN\Administrators'
            SID                                                       = 'S-1-5-32-544'
            DomainName                                                = ''
            Type                                                      = 'WellKnownAdministrative'
            Error                                                     = ''
        }
    }
    $wellKnownSIDs = @{'S-1-0' = [PSCustomObject] @{Name = 'Null AUTHORITY'
            SID                                          = 'S-1-0'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-0-0'              = [PSCustomObject] @{Name = 'NULL SID'
            SID                                          = 'S-1-0-0'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-1'                = [PSCustomObject] @{Name = 'WORLD AUTHORITY'
            SID                                          = 'S-1-1'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-1-0'              = [PSCustomObject] @{Name = 'Everyone'
            SID                                          = 'S-1-1-0'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-2'                = [PSCustomObject] @{Name = 'LOCAL AUTHORITY'
            SID                                          = 'S-1-2'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-2-0'              = [PSCustomObject] @{Name = 'LOCAL'
            SID                                          = 'S-1-2-0'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-2-1'              = [PSCustomObject] @{Name = 'CONSOLE LOGON'
            SID                                          = 'S-1-2-1'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-3'                = [PSCustomObject] @{Name = 'CREATOR AUTHORITY'
            SID                                          = 'S-1-3'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-3-0'              = [PSCustomObject] @{Name = 'CREATOR OWNER'
            SID                                          = 'S-1-3-0'
            DomainName                                   = ''
            Type                                         = 'WellKnownAdministrative'
            Error                                        = ''
        }
        'S-1-3-1'              = [PSCustomObject] @{Name = 'CREATOR GROUP'
            SID                                          = 'S-1-3-1'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-3-2'              = [PSCustomObject] @{Name = 'CREATOR OWNER SERVER'
            SID                                          = 'S-1-3-2'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-3-3'              = [PSCustomObject] @{Name = 'CREATOR GROUP SERVER'
            SID                                          = 'S-1-3-3'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-3-4'              = [PSCustomObject] @{Name = 'OWNER RIGHTS'
            SID                                          = 'S-1-3-4'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-80-0'           = [PSCustomObject] @{Name = 'NT SERVICE\ALL SERVICES'
            SID                                          = 'S-1-5-80-0'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-4'                = [PSCustomObject] @{Name = 'Non-unique Authority'
            SID                                          = 'S-1-4'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5'                = [PSCustomObject] @{Name = 'NT AUTHORITY'
            SID                                          = 'S-1-5'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-1'              = [PSCustomObject] @{Name = 'NT AUTHORITY\DIALUP'
            SID                                          = 'S-1-5-1'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-2'              = [PSCustomObject] @{Name = 'NT AUTHORITY\NETWORK'
            SID                                          = 'S-1-5-2'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-3'              = [PSCustomObject] @{Name = 'NT AUTHORITY\BATCH'
            SID                                          = 'S-1-5-3'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-4'              = [PSCustomObject] @{Name = 'NT AUTHORITY\INTERACTIVE'
            SID                                          = 'S-1-5-4'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-6'              = [PSCustomObject] @{Name = 'NT AUTHORITY\SERVICE'
            SID                                          = 'S-1-5-6'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-7'              = [PSCustomObject] @{Name = 'NT AUTHORITY\ANONYMOUS LOGON'
            SID                                          = 'S-1-5-7'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-8'              = [PSCustomObject] @{Name = 'NT AUTHORITY\PROXY'
            SID                                          = 'S-1-5-8'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-9'              = [PSCustomObject] @{Name = 'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS'
            SID                                          = 'S-1-5-9'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-10'             = [PSCustomObject] @{Name = 'NT AUTHORITY\SELF'
            SID                                          = 'S-1-5-10'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-11'             = [PSCustomObject] @{Name = 'NT AUTHORITY\Authenticated Users'
            SID                                          = 'S-1-5-11'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-12'             = [PSCustomObject] @{Name = 'NT AUTHORITY\RESTRICTED'
            SID                                          = 'S-1-5-12'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-13'             = [PSCustomObject] @{Name = 'NT AUTHORITY\TERMINAL SERVER USER'
            SID                                          = 'S-1-5-13'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-14'             = [PSCustomObject] @{Name = 'NT AUTHORITY\REMOTE INTERACTIVE LOGON'
            SID                                          = 'S-1-5-14'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-15'             = [PSCustomObject] @{Name = 'NT AUTHORITY\This Organization'
            SID                                          = 'S-1-5-15'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-17'             = [PSCustomObject] @{Name = 'NT AUTHORITY\IUSR'
            SID                                          = 'S-1-5-17'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-18'             = [PSCustomObject] @{Name = 'NT AUTHORITY\SYSTEM'
            SID                                          = 'S-1-5-18'
            DomainName                                   = ''
            Type                                         = 'WellKnownAdministrative'
            Error                                        = ''
        }
        'S-1-5-19'             = [PSCustomObject] @{Name = 'NT AUTHORITY\NETWORK SERVICE'
            SID                                          = 'S-1-5-19'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-20'             = [PSCustomObject] @{Name = 'NT AUTHORITY\NETWORK SERVICE'
            SID                                          = 'S-1-5-20'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-544'         = [PSCustomObject] @{Name = 'BUILTIN\Administrators'
            SID                                          = 'S-1-5-32-544'
            DomainName                                   = ''
            Type                                         = 'WellKnownAdministrative'
            Error                                        = ''
        }
        'S-1-5-32-545'         = [PSCustomObject] @{Name = 'BUILTIN\Users'
            SID                                          = 'S-1-5-32-545'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-546'         = [PSCustomObject] @{Name = 'BUILTIN\Guests'
            SID                                          = 'S-1-5-32-546'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-547'         = [PSCustomObject] @{Name = 'BUILTIN\Power Users'
            SID                                          = 'S-1-5-32-547'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-548'         = [PSCustomObject] @{Name = 'BUILTIN\Account Operators'
            SID                                          = 'S-1-5-32-548'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-549'         = [PSCustomObject] @{Name = 'BUILTIN\Server Operators'
            SID                                          = 'S-1-5-32-549'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-550'         = [PSCustomObject] @{Name = 'BUILTIN\Print Operators'
            SID                                          = 'S-1-5-32-550'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-551'         = [PSCustomObject] @{Name = 'BUILTIN\Backup Operators'
            SID                                          = 'S-1-5-32-551'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-552'         = [PSCustomObject] @{Name = 'BUILTIN\Replicators'
            SID                                          = 'S-1-5-32-552'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-64-10'          = [PSCustomObject] @{Name = 'NT AUTHORITY\NTLM Authentication'
            SID                                          = 'S-1-5-64-10'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-64-14'          = [PSCustomObject] @{Name = 'NT AUTHORITY\SChannel Authentication'
            SID                                          = 'S-1-5-64-14'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-64-21'          = [PSCustomObject] @{Name = 'NT AUTHORITY\Digest Authentication'
            SID                                          = 'S-1-5-64-21'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-80'             = [PSCustomObject] @{Name = 'NT SERVICE'
            SID                                          = 'S-1-5-80'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-83-0'           = [PSCustomObject] @{Name = 'NT VIRTUAL MACHINE\Virtual Machines'
            SID                                          = 'S-1-5-83-0'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-16-0'             = [PSCustomObject] @{Name = 'Untrusted Mandatory Level'
            SID                                          = 'S-1-16-0'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-16-4096'          = [PSCustomObject] @{Name = 'Low Mandatory Level'
            SID                                          = 'S-1-16-4096'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-16-8192'          = [PSCustomObject] @{Name = 'Medium Mandatory Level'
            SID                                          = 'S-1-16-8192'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-16-8448'          = [PSCustomObject] @{Name = 'Medium Plus Mandatory Level'
            SID                                          = 'S-1-16-8448'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-16-12288'         = [PSCustomObject] @{Name = 'High Mandatory Level'
            SID                                          = 'S-1-16-12288'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-16-16384'         = [PSCustomObject] @{Name = 'System Mandatory Level'
            SID                                          = 'S-1-16-16384'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-16-20480'         = [PSCustomObject] @{Name = 'Protected Process Mandatory Level'
            SID                                          = 'S-1-16-20480'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-16-28672'         = [PSCustomObject] @{Name = 'Secure Process Mandatory Level'
            SID                                          = 'S-1-16-28672'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-554'         = [PSCustomObject] @{Name = 'BUILTIN\Pre-Windows 2000 Compatible Access'
            SID                                          = 'S-1-5-32-554'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-555'         = [PSCustomObject] @{Name = 'BUILTIN\Remote Desktop Users'
            SID                                          = 'S-1-5-32-555'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-556'         = [PSCustomObject] @{Name = 'BUILTIN\Network Configuration Operators'
            SID                                          = 'S-1-5-32-556'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-557'         = [PSCustomObject] @{Name = 'BUILTIN\Incoming Forest Trust Builders'
            SID                                          = 'S-1-5-32-557'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-558'         = [PSCustomObject] @{Name = 'BUILTIN\Performance Monitor Users'
            SID                                          = 'S-1-5-32-558'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-559'         = [PSCustomObject] @{Name = 'BUILTIN\Performance Log Users'
            SID                                          = 'S-1-5-32-559'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-560'         = [PSCustomObject] @{Name = 'BUILTIN\Windows Authorization Access Group'
            SID                                          = 'S-1-5-32-560'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-561'         = [PSCustomObject] @{Name = 'BUILTIN\Terminal Server License Servers'
            SID                                          = 'S-1-5-32-561'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-562'         = [PSCustomObject] @{Name = 'BUILTIN\Distributed COM Users'
            SID                                          = 'S-1-5-32-562'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-569'         = [PSCustomObject] @{Name = 'BUILTIN\Cryptographic Operators'
            SID                                          = 'S-1-5-32-569'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-573'         = [PSCustomObject] @{Name = 'BUILTIN\Event Log Readers'
            SID                                          = 'S-1-5-32-573'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-574'         = [PSCustomObject] @{Name = 'BUILTIN\Certificate Service DCOM Access'
            SID                                          = 'S-1-5-32-574'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-575'         = [PSCustomObject] @{Name = 'BUILTIN\RDS Remote Access Servers'
            SID                                          = 'S-1-5-32-575'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-576'         = [PSCustomObject] @{Name = 'BUILTIN\RDS Endpoint Servers'
            SID                                          = 'S-1-5-32-576'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-577'         = [PSCustomObject] @{Name = 'BUILTIN\RDS Management Servers'
            SID                                          = 'S-1-5-32-577'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-578'         = [PSCustomObject] @{Name = 'BUILTIN\Hyper-V Administrators'
            SID                                          = 'S-1-5-32-578'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-579'         = [PSCustomObject] @{Name = 'BUILTIN\Access Control Assistance Operators'
            SID                                          = 'S-1-5-32-579'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-5-32-580'         = [PSCustomObject] @{Name = 'BUILTIN\Remote Management Users'
            SID                                          = 'S-1-5-32-580'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
    }
    foreach ($S in $SID) {
        if ($OnlyWellKnownAdministrative) { if ($WellKnownAdministrative[$S]) { $WellKnownAdministrative[$S] } } elseif ($OnlyWellKnown) { if ($wellKnownSIDs[$S]) { $wellKnownSIDs[$S] } } else {
            if ($wellKnownSIDs[$S]) { $wellKnownSIDs[$S] } else {
                if ($DoNotResolve) {
                    if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512") {
                        [PSCustomObject] @{Name = $S
                            SID                 = $S
                            DomainName          = ''
                            Type                = 'Administrative'
                            Error               = ''
                        }
                    } else {
                        [PSCustomObject] @{Name = $S
                            SID                 = $S
                            DomainName          = ''
                            Error               = ''
                            Type                = 'NotAdministrative'
                        }
                    }
                } else {
                    try {
                        if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512") { $Type = 'Administrative' } else { $Type = 'NotAdministrative' }
                        $Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value
                        [PSCustomObject] @{Name = $Name
                            SID                 = $S
                            DomainName          = (ConvertFrom-NetbiosName -Identity $Name).DomainName
                            Type                = $Type
                            Error               = ''
                        }
                    } catch {
                        [PSCustomObject] @{Name = $S
                            SID                 = $S
                            DomainName          = ''
                            Error               = $_.Exception.Message -replace [environment]::NewLine, ' '
                            Type                = 'Unknown'
                        }
                    }
                }
            }
        }
    }
}
function Convert-Identity {
    <#
    .SYNOPSIS
    Small command that tries to resolve any given object
 
    .DESCRIPTION
    Small command that tries to resolve any given object - be it SID, DN, FSP or Netbiosname
 
    .PARAMETER Identity
    Type to resolve in form of Identity, DN, SID
 
    .PARAMETER SID
    Allows to pass SID directly, rather then going thru verification process
 
    .PARAMETER Name
    Allows to pass Name directly, rather then going thru verification process
 
    .EXAMPLE
    $Identity = @(
        'S-1-5-4'
        'S-1-5-4'
        'S-1-5-11'
        'S-1-5-32-549'
        'S-1-5-32-550'
        'S-1-5-32-548'
        'S-1-5-64-10'
        'S-1-5-64-14'
        'S-1-5-64-21'
        'S-1-5-18'
        'S-1-5-19'
        'S-1-5-32-544'
        'S-1-5-20-20-10-51' # Wrong SID
        'S-1-5-21-853615985-2870445339-3163598659-512'
        'S-1-5-21-3661168273-3802070955-2987026695-512'
        'S-1-5-21-1928204107-2710010574-1926425344-512'
        'CN=Test Test 2,OU=Users,OU=Production,DC=ad,DC=evotec,DC=pl'
        'Test Local Group'
        'przemyslaw.klys@evotec.pl'
        'test2'
        'NT AUTHORITY\NETWORK'
        'NT AUTHORITY\SYSTEM'
        'S-1-5-21-853615985-2870445339-3163598659-519'
        'TEST\some'
        'EVOTECPL\Domain Admins'
        'NT AUTHORITY\INTERACTIVE'
        'INTERACTIVE'
        'EVOTEC\Domain Admins'
        'EVOTECPL\Domain Admins'
        'Test\Domain Admins'
        'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # Valid
        'CN=S-1-5-21-1928204107-2710010574-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # not valid
        'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # cached
    )
 
    $TestOutput = Convert-Identity -Identity $Identity -Verbose
 
    Output:
 
    Name SID DomainName Type Error
    ---- --- ---------- ---- -----
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    NT AUTHORITY\Authenticated Users S-1-5-11 WellKnownGroup
    BUILTIN\Server Operators S-1-5-32-549 WellKnownGroup
    BUILTIN\Print Operators S-1-5-32-550 WellKnownGroup
    BUILTIN\Account Operators S-1-5-32-548 WellKnownGroup
    NT AUTHORITY\NTLM Authentication S-1-5-64-10 WellKnownGroup
    NT AUTHORITY\SChannel Authentication S-1-5-64-14 WellKnownGroup
    NT AUTHORITY\Digest Authentication S-1-5-64-21 WellKnownGroup
    NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative
    NT AUTHORITY\NETWORK SERVICE S-1-5-19 WellKnownGroup
    BUILTIN\Administrators S-1-5-32-544 WellKnownAdministrative
    S-1-5-20-20-10-51 S-1-5-20-20-10-51 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated."
    EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative
    EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
    EVOTECPL\TestingAD S-1-5-21-3661168273-3802070955-2987026695-1111 ad.evotec.pl NotAdministrative
    EVOTEC\Test Local Group S-1-5-21-853615985-2870445339-3163598659-3610 ad.evotec.xyz NotAdministrative
    EVOTEC\przemyslaw.klys S-1-5-21-853615985-2870445339-3163598659-1105 ad.evotec.xyz NotAdministrative
    test2 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated."
    NT AUTHORITY\NETWORK S-1-5-2 WellKnownGroup
    NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative
    EVOTEC\Enterprise Admins S-1-5-21-853615985-2870445339-3163598659-519 ad.evotec.xyz Administrative
    TEST\some S-1-5-21-1928204107-2710010574-1926425344-1106 test.evotec.pl NotAdministrative
    EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative
    EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
    S-1-5-21-1928204107-2710010574-512 S-1-5-21-1928204107-2710010574-512 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated."
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'Identity')]
    param([parameter(ParameterSetName = 'Identity', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)][string[]] $Identity,
        [parameter(ParameterSetName = 'SID', Mandatory)][System.Security.Principal.SecurityIdentifier[]] $SID,
        [parameter(ParameterSetName = 'Name', Mandatory)][string[]] $Name)
    Begin { if (-not $Script:GlobalCacheSidConvert) { $Script:GlobalCacheSidConvert = @{} } }
    Process {
        if ($Identity) {
            foreach ($Ident in $Identity) {
                $MatchRegex = [Regex]::Matches($Ident, "S-\d-\d+-(\d+-|){1,14}\d+")
                if ($Script:GlobalCacheSidConvert[$Ident]) {
                    Write-Verbose "Convert-Identity - Processing $Ident (Cache)"
                    $Script:GlobalCacheSidConvert[$Ident]
                } elseif ($MatchRegex.Success) {
                    Write-Verbose "Convert-Identity - Processing $Ident (SID)"
                    if ($MatchRegex.Value -ne $Ident) { $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $MatchRegex.Value } else { $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $Ident }
                    $Script:GlobalCacheSidConvert[$Ident]
                } elseif ($Ident -like '*DC=*') {
                    Write-Verbose "Convert-Identity - Processing $Ident (DistinguishedName)"
                    try {
                        $Object = [adsi]"LDAP://$($Ident)"
                        $SIDValue = [System.Security.Principal.SecurityIdentifier]::new($Object.objectSid.Value, 0).Value
                        $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue
                    } catch {
                        $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Ident
                            SID                                                         = $null
                            DomainName                                                  = ''
                            Type                                                        = 'Unknown'
                            Error                                                       = $_.Exception.Message -replace [environment]::NewLine, ' '
                        }
                    }
                    $Script:GlobalCacheSidConvert[$Ident]
                } else {
                    Write-Verbose "Convert-Identity - Processing $Ident (Other)"
                    try {
                        $SIDValue = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value
                        $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue
                    } catch {
                        $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Ident
                            SID                                                         = $null
                            DomainName                                                  = ''
                            Type                                                        = 'Unknown'
                            Error                                                       = $_.Exception.Message -replace [environment]::NewLine, ' '
                        }
                    }
                    $Script:GlobalCacheSidConvert[$Ident]
                }
            }
        } else {
            if ($SID) {
                foreach ($S in $SID) {
                    if ($Script:GlobalCacheSidConvert[$S]) { $Script:GlobalCacheSidConvert[$S] } else {
                        $Script:GlobalCacheSidConvert[$S] = ConvertFrom-SID -SID $S
                        $Script:GlobalCacheSidConvert[$S]
                    }
                }
            } else {
                foreach ($Ident in $Name) {
                    if ($Script:GlobalCacheSidConvert[$Ident]) { $Script:GlobalCacheSidConvert[$Ident] } else {
                        $Script:GlobalCacheSidConvert[$Ident] = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value
                        $Script:GlobalCacheSidConvert[$Ident]
                    }
                }
            }
        }
    }
    End {}
}
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-DistinguishedName {
    <#
    .SYNOPSIS
    Converts CanonicalName to DistinguishedName
 
    .DESCRIPTION
    Converts CanonicalName to DistinguishedName for 3 different options
 
    .PARAMETER CanonicalName
    One or multiple canonical names
 
    .PARAMETER ToOU
    Converts CanonicalName to OrganizationalUnit DistinguishedName
 
    .PARAMETER ToObject
    Converts CanonicalName to Full Object DistinguishedName
 
    .PARAMETER ToDomain
    Converts CanonicalName to Domain DistinguishedName
 
    .EXAMPLE
 
    $CanonicalObjects = @(
    'ad.evotec.xyz/Production/Groups/Security/ITR03_AD Admins'
    'ad.evotec.xyz/Production/Accounts/Special/SADM Testing 2'
    )
    $CanonicalOU = @(
        'ad.evotec.xyz/Production/Groups/Security/NetworkAdministration'
        'ad.evotec.xyz/Production'
    )
 
    $CanonicalDomain = @(
        'ad.evotec.xyz/Production/Groups/Security/ITR03_AD Admins'
        'ad.evotec.pl'
        'ad.evotec.xyz'
        'test.evotec.pl'
        'ad.evotec.xyz/Production'
    )
    $CanonicalObjects | ConvertTo-DistinguishedName -ToObject
    $CanonicalOU | ConvertTo-DistinguishedName -ToOU
    $CanonicalDomain | ConvertTo-DistinguishedName -ToDomain
 
    Output:
    CN=ITR03_AD Admins,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz
    CN=SADM Testing 2,OU=Special,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz
    Output2:
    OU=NetworkAdministration,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz
    OU=Production,DC=ad,DC=evotec,DC=xyz
    Output3:
    DC=ad,DC=evotec,DC=xyz
    DC=ad,DC=evotec,DC=pl
    DC=ad,DC=evotec,DC=xyz
    DC=test,DC=evotec,DC=pl
    DC=ad,DC=evotec,DC=xyz
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'ToDomain')]
    param([Parameter(ParameterSetName = 'ToOU')]
        [Parameter(ParameterSetName = 'ToObject')]
        [Parameter(ParameterSetName = 'ToDomain')]
        [alias('Identity', 'CN')][Parameter(ValueFromPipeline, Mandatory, ValueFromPipelineByPropertyName, Position = 0)][string[]] $CanonicalName,
        [Parameter(ParameterSetName = 'ToOU')][switch] $ToOU,
        [Parameter(ParameterSetName = 'ToObject')][switch] $ToObject,
        [Parameter(ParameterSetName = 'ToDomain')][switch] $ToDomain)
    Process {
        foreach ($CN in $CanonicalName) {
            if ($ToObject) {
                $ADObject = $CN.Replace(',', '\,').Split('/')
                [string]$DN = "CN=" + $ADObject[$ADObject.count - 1]
                for ($i = $ADObject.count - 2; $i -ge 1; $i--) { $DN += ",OU=" + $ADObject[$i] }
                $ADObject[0].split(".") | ForEach-Object { $DN += ",DC=" + $_ }
            } elseif ($ToOU) {
                $ADObject = $CN.Replace(',', '\,').Split('/')
                [string]$DN = "OU=" + $ADObject[$ADObject.count - 1]
                for ($i = $ADObject.count - 2; $i -ge 1; $i--) { $DN += ",OU=" + $ADObject[$i] }
                $ADObject[0].split(".") | ForEach-Object { $DN += ",DC=" + $_ }
            } else {
                $ADObject = $CN.Replace(',', '\,').Split('/')
                $DN = 'DC=' + $ADObject[0].Replace('.', ',DC=')
            }
            $DN
        }
    }
}
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 (19043)' = 'Windows 10 21H1'
            '10.0 (19042)'          = 'Windows 10 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.19043'            = 'Windows 10 21H1'
            '10.0.19042'            = 'Windows 10 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 Convert-UserAccountControl {
    [cmdletBinding()]
    param([alias('UAC')][int] $UserAccountControl,
        [string] $Separator)
    $UserAccount = [ordered] @{"SCRIPT"  = 1
        "ACCOUNTDISABLE"                 = 2
        "HOMEDIR_REQUIRED"               = 8
        "LOCKOUT"                        = 16
        "PASSWD_NOTREQD"                 = 32
        "ENCRYPTED_TEXT_PWD_ALLOWED"     = 128
        "TEMP_DUPLICATE_ACCOUNT"         = 256
        "NORMAL_ACCOUNT"                 = 512
        "INTERDOMAIN_TRUST_ACCOUNT"      = 2048
        "WORKSTATION_TRUST_ACCOUNT"      = 4096
        "SERVER_TRUST_ACCOUNT"           = 8192
        "DONT_EXPIRE_PASSWORD"           = 65536
        "MNS_LOGON_ACCOUNT"              = 131072
        "SMARTCARD_REQUIRED"             = 262144
        "TRUSTED_FOR_DELEGATION"         = 524288
        "NOT_DELEGATED"                  = 1048576
        "USE_DES_KEY_ONLY"               = 2097152
        "DONT_REQ_PREAUTH"               = 4194304
        "PASSWORD_EXPIRED"               = 8388608
        "TRUSTED_TO_AUTH_FOR_DELEGATION" = 16777216
        "PARTIAL_SECRETS_ACCOUNT"        = 67108864
    }
    $Output = foreach ($_ in $UserAccount.Keys) {
        $binaryAnd = $UserAccount[$_] -band $UserAccountControl
        if ($binaryAnd -ne "0") { $_ }
    }
    if ($Separator) { $Output -join $Separator } else { $Output }
}
function Copy-Dictionary {
    [alias('Copy-Hashtable', 'Copy-OrderedHashtable')]
    [cmdletbinding()]
    param([System.Collections.IDictionary] $Dictionary)
    $ms = [System.IO.MemoryStream]::new()
    $bf = [System.Runtime.Serialization.Formatters.Binary.BinaryFormatter]::new()
    $bf.Serialize($ms, $Dictionary)
    $ms.Position = 0
    $clone = $bf.Deserialize($ms)
    $ms.Close()
    $clone
}
function Get-ADADministrativeGroups {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Type
    Parameter description
 
    .PARAMETER Forest
    Parameter description
 
    .PARAMETER ExcludeDomains
    Parameter description
 
    .PARAMETER IncludeDomains
    Parameter description
 
    .PARAMETER ExtendedForestInformation
    Parameter description
 
    .EXAMPLE
    Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins
 
    Output (Where VALUE is Get-ADGroup output):
    Name Value
    ---- -----
    ByNetBIOS {EVOTEC\Domain Admins, EVOTEC\Enterprise Admins, EVOTECPL\Domain Admins}
    ad.evotec.xyz {DomainAdmins, EnterpriseAdmins}
    ad.evotec.pl {DomainAdmins}
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param([parameter(Mandatory)][validateSet('DomainAdmins', 'EnterpriseAdmins')][string[]] $Type,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ADDictionary = [ordered] @{}
    $ADDictionary['ByNetBIOS'] = [ordered] @{}
    $ADDictionary['BySID'] = [ordered] @{}
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $ADDictionary[$Domain] = [ordered] @{}
        $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
        $DomainInformation = Get-ADDomain -Server $QueryServer
        if ($Type -contains 'DomainAdmins') {
            Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-512'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object { $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_
                $ADDictionary[$Domain]['DomainAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)"
                $ADDictionary['BySID'][$_.SID.Value] = $_ }
        }
    }
    foreach ($Domain in $ForestInformation.Forest.Domains) {
        if (-not $ADDictionary[$Domain]) { $ADDictionary[$Domain] = [ordered] @{} }
        if ($Type -contains 'EnterpriseAdmins') {
            $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
            $DomainInformation = Get-ADDomain -Server $QueryServer
            Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-519'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object { $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_
                $ADDictionary[$Domain]['EnterpriseAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)"
                $ADDictionary['BySID'][$_.SID.Value] = $_ }
        }
    }
    return $ADDictionary
}
function Get-ADEncryptionTypes {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Value
    Parameter description
 
    .EXAMPLE
    Get-ADEncryptionTypes -Value 24
 
    Output:
    AES128-CTS-HMAC-SHA1-96
    AES256-CTS-HMAC-SHA1-96
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    Param([parameter(Mandatory = $false, ValueFromPipeline = $True)][int32]$Value)
    [String[]]$EncryptionTypes = @(Foreach ($V in $Value) {
            if ([int32]$V -band 0x00000001) { "DES-CBC-CRC" }
            if ([int32]$V -band 0x00000002) { "DES-CBC-MD5" }
            if ([int32]$V -band 0x00000004) { "RC4-HMAC" }
            if ([int32]$V -band 0x00000008) { "AES128-CTS-HMAC-SHA1-96" }
            if ([int32]$V -band 0x00000010) { "AES256-CTS-HMAC-SHA1-96" }
            if ([int32]$V -band 0x00000020) { "FAST-supported" }
            if ([int32]$V -band 0x00000040) { "Compound-identity-supported" }
            if ([int32]$V -band 0x00000080) { "Claims-supported" }
            if ([int32]$V -band 0x00000200) { "Resource-SID-compression-disabled" }
        })
    $EncryptionTypes
}
function Get-ADTrustAttributes {
    [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 Only" }
            if ([int32]$V -band 0x00000004) { "Quarantined Domain" }
            if ([int32]$V -band 0x00000008) { "Forest Transitive" }
            if ([int32]$V -band 0x00000010) { "Cross Organization" }
            if ([int32]$V -band 0x00000020) { "Within Forest" }
            if ([int32]$V -band 0x00000040) { "Treat as External" }
            if ([int32]$V -band 0x00000080) { "Uses RC4 Encryption" }
            if ([int32]$V -band 0x00000200) { "No TGT DELEGATION" }
            if ([int32]$V -band 0x00000800) { "Enable TGT DELEGATION" }
            if ([int32]$V -band 0x00000400) { "PIM Trust" }
        })
    return $TrustAttributes
}
function Get-CimData {
    <#
    .SYNOPSIS
    Helper function for retreiving CIM data from local and remote computers
 
    .DESCRIPTION
    Helper function for retreiving CIM data from local and remote computers
 
    .PARAMETER ComputerName
    Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name (FQDN), a NetBIOS name, or an IP address. If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component Object Model (COM).
 
    .PARAMETER Protocol
    Specifies the protocol to use. The acceptable values for this parameter are: DCOM, Default, or Wsman.
 
    .PARAMETER Class
    Specifies the name of the CIM class for which to retrieve the CIM instances. You can use tab completion to browse the list of classes, because PowerShell gets a list of classes from the local WMI server to provide a list of class names.
 
    .PARAMETER Properties
    Specifies a set of instance properties to retrieve. Use this parameter when you need to reduce the size of the object returned, either in memory or over the network. The object returned also contains the key properties even if you have not listed them using the Property parameter. Other properties of the class are present but they are not populated.
 
    .EXAMPLE
    Get-CimData -Class 'win32_bios' -ComputerName AD1,EVOWIN
 
    Get-CimData -Class 'win32_bios'
 
    # Get-CimClass to get all classes
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([parameter(Mandatory)][string] $Class,
        [string] $NameSpace = 'root\cimv2',
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [string[]] $Properties = '*')
    $ExcludeProperties = 'CimClass', 'CimInstanceProperties', 'CimSystemProperties', 'SystemCreationClassName', 'CreationClassName'
    [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName
    $CimObject = @(# requires removal of this property for query
        [string[]] $PropertiesOnly = $Properties | Where-Object { $_ -ne 'PSComputerName' }
        $Computers = $ComputersSplit[1]
        if ($Computers.Count -gt 0) {
            if ($Protocol = 'Default') { Get-CimInstance -ClassName $Class -ComputerName $Computers -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties } else {
                $Option = New-CimSessionOption -Protocol $Protocol
                $Session = New-CimSession -ComputerName $Computers -SessionOption $Option -ErrorAction SilentlyContinue
                $Info = Get-CimInstance -ClassName $Class -CimSession $Session -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
                $null = Remove-CimSession -CimSession $Session -ErrorAction SilentlyContinue
                $Info
            }
        }
        foreach ($E in $ErrorsToProcess) { Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)" }
        $Computers = $ComputersSplit[0]
        if ($Computers.Count -gt 0) {
            $Info = Get-CimInstance -ClassName $Class -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsLocal | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
            $Info | Add-Member -Name 'PSComputerName' -Value $Computers -MemberType NoteProperty -Force
            $Info
        }
        foreach ($E in $ErrorsLocal) { Write-Warning -Message "Get-CimData - No data for computer $($Env:COMPUTERNAME). Failed with errror: $($E.Exception.Message)" })
    $CimObject
}
function Get-FileName {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Extension
    Parameter description
 
    .PARAMETER Temporary
    Parameter description
 
    .PARAMETER TemporaryFileOnly
    Parameter description
 
    .EXAMPLE
    Get-FileName -Temporary
    Output: 3ymsxvav.tmp
 
    .EXAMPLE
 
    Get-FileName -Temporary
    Output: C:\Users\pklys\AppData\Local\Temp\tmpD74C.tmp
 
    .EXAMPLE
 
    Get-FileName -Temporary -Extension 'xlsx'
    Output: C:\Users\pklys\AppData\Local\Temp\tmp45B6.xlsx
 
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string] $Extension = 'tmp',
        [switch] $Temporary,
        [switch] $TemporaryFileOnly)
    if ($Temporary) { return "$($([System.IO.Path]::GetTempFileName()).Replace('.tmp','')).$Extension" }
    if ($TemporaryFileOnly) { return "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension" }
}
function Get-FileOwner {
    [cmdletBinding()]
    param([Array] $Path,
        [switch] $Recursive,
        [switch] $JustPath,
        [switch] $Resolve,
        [switch] $AsHashTable)
    Begin {}
    Process {
        foreach ($P in $Path) {
            if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P }
            if ($FullPath -and (Test-Path -Path $FullPath)) {
                if ($JustPath) {
                    $FullPath | ForEach-Object -Process { $ACL = Get-Acl -Path $_
                        $Object = [ordered]@{FullName = $_
                            Owner                     = $ACL.Owner
                        }
                        if ($Resolve) {
                            $Identity = Convert-Identity -Identity $ACL.Owner
                            if ($Identity) {
                                $Object['OwnerName'] = $Identity.Name
                                $Object['OwnerSid'] = $Identity.SID
                                $Object['OwnerType'] = $Identity.Type
                            } else {
                                $Object['OwnerName'] = ''
                                $Object['OwnerSid'] = ''
                                $Object['OwnerType'] = ''
                            }
                        }
                        if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } }
                } else {
                    Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -Force | ForEach-Object -Process { $File = $_
                        $ACL = Get-Acl -Path $File.FullName
                        $Object = [ordered] @{FullName = $_.FullName
                            Extension                  = $_.Extension
                            CreationTime               = $_.CreationTime
                            LastAccessTime             = $_.LastAccessTime
                            LastWriteTime              = $_.LastWriteTime
                            Attributes                 = $_.Attributes
                            Owner                      = $ACL.Owner
                        }
                        if ($Resolve) {
                            $Identity = Convert-Identity -Identity $ACL.Owner
                            if ($Identity) {
                                $Object['OwnerName'] = $Identity.Name
                                $Object['OwnerSid'] = $Identity.SID
                                $Object['OwnerType'] = $Identity.Type
                            } else {
                                $Object['OwnerName'] = ''
                                $Object['OwnerSid'] = ''
                                $Object['OwnerType'] = ''
                            }
                        }
                        if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } }
                }
            }
        }
    }
    End {}
}
function Get-FilePermission {
    [alias('Get-PSPermissions', 'Get-FilePermissions')]
    [cmdletBinding()]
    param([Array] $Path,
        [switch] $Inherited,
        [switch] $NotInherited,
        [switch] $ResolveTypes,
        [switch] $Extended,
        [switch] $IncludeACLObject,
        [switch] $AsHashTable,
        [System.Security.AccessControl.FileSystemSecurity] $ACLS)
    foreach ($P in $Path) {
        if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P }
        $TestPath = Test-Path -Path $FullPath
        if ($TestPath) {
            if (-not $ACLS) {
                try { $ACLS = (Get-Acl -Path $FullPath -ErrorAction Stop) } catch {
                    Write-Warning -Message "Get-FilePermission - Can't access $FullPath. Error $($_.Exception.Message)"
                    continue
                }
            }
            $Output = foreach ($ACL in $ACLS.Access) {
                if ($Inherited) { if ($ACL.IsInherited -eq $false) { continue } }
                if ($NotInherited) { if ($ACL.IsInherited -eq $true) { continue } }
                $TranslateRights = Convert-GenericRightsToFileSystemRights -OriginalRights $ACL.FileSystemRights
                $ReturnObject = [ordered] @{}
                $ReturnObject['Path' ] = $FullPath
                $ReturnObject['AccessControlType'] = $ACL.AccessControlType
                if ($ResolveTypes) {
                    $Identity = Convert-Identity -Identity $ACL.IdentityReference
                    if ($Identity) {
                        $ReturnObject['Principal'] = $ACL.IdentityReference
                        $ReturnObject['PrincipalName'] = $Identity.Name
                        $ReturnObject['PrincipalSid'] = $Identity.Sid
                        $ReturnObject['PrincipalType'] = $Identity.Type
                    } else {
                        $ReturnObject['Principal'] = $Identity
                        $ReturnObject['PrincipalName'] = ''
                        $ReturnObject['PrincipalSid'] = ''
                        $ReturnObject['PrincipalType'] = ''
                    }
                } else { $ReturnObject['Principal'] = $ACL.IdentityReference.Value }
                $ReturnObject['FileSystemRights'] = $TranslateRights
                $ReturnObject['IsInherited'] = $ACL.IsInherited
                if ($Extended) {
                    $ReturnObject['InheritanceFlags'] = $ACL.InheritanceFlags
                    $ReturnObject['PropagationFlags'] = $ACL.PropagationFlags
                }
                if ($IncludeACLObject) {
                    $ReturnObject['ACL'] = $ACL
                    $ReturnObject['AllACL'] = $ACLS
                }
                if ($AsHashTable) { $ReturnObject } else { [PSCustomObject] $ReturnObject }
            }
            $Output
        } else { Write-Warning "Get-PSPermissions - Path $Path doesn't exists. Skipping." }
    }
}
function Get-GitHubLatestRelease {
    [CmdLetBinding()]
    param([alias('ReleasesUrl')][uri] $Url)
    $ProgressPreference = 'SilentlyContinue'
    Try {
        [Array] $JsonOutput = (Invoke-WebRequest -Uri $Url -ErrorAction Stop | ConvertFrom-Json)
        foreach ($JsonContent in $JsonOutput) {
            [PSCustomObject] @{PublishDate = [DateTime] $JsonContent.published_at
                CreatedDate                = [DateTime] $JsonContent.created_at
                PreRelease                 = [bool] $JsonContent.prerelease
                Version                    = [version] ($JsonContent.name -replace 'v', '')
                Tag                        = $JsonContent.tag_name
                Branch                     = $JsonContent.target_commitish
                Errors                     = ''
            }
        }
    } catch {
        [PSCustomObject] @{PublishDate = $null
            CreatedDate                = $null
            PreRelease                 = $null
            Version                    = $null
            Tag                        = $null
            Branch                     = $null
            Errors                     = $_.Exception.Message
        }
    }
    $ProgressPreference = 'Continue'
}
function Get-PSRegistry {
    [cmdletbinding()]
    param([alias('Path')][string[]] $RegistryPath,
        [string[]] $ComputerName = $Env:COMPUTERNAME)
    $RootKeyDictionary = @{HKEY_CLASSES_ROOT = 2147483648
        HKCR                                 = 2147483648
        HKEY_CURRENT_USER                    = 2147483649
        HKCU                                 = 2147483649
        HKEY_LOCAL_MACHINE                   = 2147483650
        HKLM                                 = 2147483650
        HKEY_USERS                           = 2147483651
        HKU                                  = 2147483651
        HKEY_CURRENT_CONFIG                  = 2147483653
        HKCC                                 = 2147483653
        HKEY_DYN_DATA                        = 2147483654
        HKDD                                 = 2147483654
    }
    $TypesDictionary = @{'1' = 'GetStringValue'
        '2'                  = 'GetExpandedStringValue'
        '3'                  = 'GetBinaryValue'
        '4'                  = 'GetDWORDValue'
        '7'                  = 'GetMultiStringValue'
        '11'                 = 'GetQWORDValue'
    }
    $Dictionary = @{'HKCR:' = 'HKEY_CLASSES_ROOT'
        'HKCU:'             = 'HKEY_CURRENT_USER'
        'HKLM:'             = 'HKEY_LOCAL_MACHINE'
        'HKU:'              = 'HKEY_USERS'
        'HKCC:'             = 'HKEY_CURRENT_CONFIG'
        'HKDD:'             = 'HKEY_DYN_DATA'
    }
    [uint32] $RootKey = $null
    [Array] $Computers = Get-ComputerSplit -ComputerName $ComputerName
    foreach ($Registry in $RegistryPath) {
        If ($Registry -like '*:*') {
            foreach ($Key in $Dictionary.Keys) {
                if ($Registry.StartsWith($Key, [System.StringComparison]::CurrentCultureIgnoreCase)) {
                    $Registry = $Registry -replace $Key, $Dictionary[$Key]
                    break
                }
            }
        }
        for ($ComputerSplit = 0; $ComputerSplit -lt $Computers.Count; $ComputerSplit++) {
            if ($Computers[$ComputerSplit].Count -gt 0) {
                $Arguments = foreach ($_ in $RootKeyDictionary.Keys) {
                    if ($Registry.StartsWith($_, [System.StringComparison]::CurrentCultureIgnoreCase)) {
                        $RootKey = [uint32] $RootKeyDictionary[$_]
                        @{hDefKey       = [uint32] $RootKeyDictionary[$_]
                            sSubKeyName = $Registry.substring($_.Length + 1)
                        }
                        break
                    }
                }
                if ($ComputerSplit -eq 0) {
                    $Output2 = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumValues -Arguments $Arguments -Verbose:$false
                    $OutputKeys = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumKey -Arguments $Arguments -Verbose:$false
                } else {
                    $Output2 = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumValues -Arguments $Arguments -ComputerName $Computers[$ComputerSplit] -Verbose:$false
                    $OutputKeys = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumKey -ComputerName $Computers[$ComputerSplit] -Arguments $Arguments -Verbose:$false
                }
                foreach ($Entry in $Output2) {
                    $RegistryOutput = [ordered] @{}
                    if ($Entry.ReturnValue -ne 0) { $RegistryOutput['PSError'] = $true } else {
                        $RegistryOutput['PSError'] = $false
                        $Types = $Entry.Types
                        $Names = $Entry.sNames
                        for ($i = 0; $i -lt $Names.Count; $i++) {
                            $Arguments['sValueName'] = $Names[$i]
                            $MethodName = $TypesDictionary["$($Types[$i])"]
                            if ($ComputerSplit -eq 0) { $Values = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -Verbose:$false } else { $Values = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -ComputerName $Entry.PSComputerName -Verbose:$false }
                            if ($null -ne $Values.sValue) { if ($Names[$i]) { $RegistryOutput[$Names[$i]] = $Values.sValue } else { $RegistryOutput['DefaultKey'] = $Values.sValue } } elseif ($null -ne $Values.uValue) { if ($Names[$i]) { $RegistryOutput[$Names[$i]] = $Values.uValue } else { $RegistryOutput['DefaultKey'] = $Values.sValue } }
                        }
                    }
                    if (-not $RegistryOutput['PSComputerName']) { if ($ComputerSplit -eq 0) { $RegistryOutput['PSComputerName'] = $ENV:COMPUTERNAME } else { $RegistryOutput['PSComputerName'] = $Entry.PSComputerName } } else { if ($ComputerSplit -eq 0) { $RegistryOutput['ComputerName'] = $ENV:COMPUTERNAME } else { $RegistryOutput['ComputerName'] = $Entry.PSComputerName } }
                    if (-not $RegistryOutput['PSSubKeys']) { $RegistryOutput['PSSubKeys'] = $OutputKeys.sNames } else { $RegistryOutput['SubKeys'] = $OutputKeys.sNames }
                    $RegistryOutput['PSPath'] = $Registry
                    [PSCustomObject] $RegistryOutput
                }
            }
        }
    }
}
function Get-RandomStringName {
    [cmdletbinding()]
    param([int] $Size = 31,
        [switch] $ToLower,
        [switch] $ToUpper,
        [switch] $LettersOnly)
    [string] $MyValue = @(if ($LettersOnly) { ( -join ((1..$Size) | ForEach-Object { (65..90) + (97..122) | Get-Random } | ForEach-Object { [char]$_ })) } else { ( -join ((48..57) + (97..122) | Get-Random -Count $Size | ForEach-Object { [char]$_ })) })
    if ($ToLower) { return $MyValue.ToLower() }
    if ($ToUpper) { return $MyValue.ToUpper() }
    return $MyValue
}
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 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 Remove-EmptyValue {
    [alias('Remove-EmptyValues')]
    [CmdletBinding()]
    param([alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable,
        [string[]] $ExcludeParameter,
        [switch] $Recursive,
        [int] $Rerun,
        [switch] $DoNotRemoveNull,
        [switch] $DoNotRemoveEmpty,
        [switch] $DoNotRemoveEmptyArray,
        [switch] $DoNotRemoveEmptyDictionary)
    foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } }
    if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } }
}
function Rename-LatinCharacters {
    [alias('Remove-StringLatinCharacters')]
    param ([string]$String)
    [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($String))
}
function Set-FileOwner {
    [cmdletBinding(SupportsShouldProcess)]
    param([Array] $Path,
        [switch] $Recursive,
        [string] $Owner,
        [string[]] $Exlude,
        [switch] $JustPath)
    Begin {}
    Process {
        foreach ($P in $Path) {
            if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P }
            $OwnerTranslated = [System.Security.Principal.NTAccount]::new($Owner)
            if ($FullPath -and (Test-Path -Path $FullPath)) {
                if ($JustPath) {
                    $FullPath | ForEach-Object -Process { $File = $_
                        try { $ACL = Get-Acl -Path $File -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)" }
                        if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) {
                            if ($PSCmdlet.ShouldProcess($File, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) {
                                try {
                                    $ACL.SetOwner($OwnerTranslated)
                                    Set-Acl -Path $File -AclObject $ACL -ErrorAction Stop
                                } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" }
                            }
                        } }
                } else {
                    Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -ErrorAction SilentlyContinue -ErrorVariable err | ForEach-Object -Process { $File = $_
                        try { $ACL = Get-Acl -Path $File.FullName -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)" }
                        if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) {
                            if ($PSCmdlet.ShouldProcess($File.FullName, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) {
                                try {
                                    $ACL.SetOwner($OwnerTranslated)
                                    Set-Acl -Path $File.FullName -AclObject $ACL -ErrorAction Stop
                                } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" }
                            }
                        } }
                    foreach ($e in $err) { Write-Warning "Set-FileOwner - Errors processing $($e.Exception.Message) ($($e.CategoryInfo.Reason))" }
                }
            }
        }
    }
    End {}
}
function Set-PSRegistry {
    [cmdletbinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME,
        [Parameter(Mandatory)][string] $RegistryPath,
        [Parameter(Mandatory)][ValidateSet('REG_SZ', 'REG_EXPAND_SZ', 'REG_BINARY', 'REG_DWORD', 'REG_MULTI_SZ', 'REG_QWORD')][string] $Type,
        [Parameter(Mandatory)][string] $Key,
        [Parameter(Mandatory)][object] $Value)
    [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName
    [uint32] $RootKey = $null
    $RootKeyDictionary = @{HKEY_CLASSES_ROOT = 2147483648
        HKCR                                 = 2147483648
        HKEY_CURRENT_USER                    = 2147483649
        HKCU                                 = 2147483649
        HKEY_LOCAL_MACHINE                   = 2147483650
        HKLM                                 = 2147483650
        HKEY_USERS                           = 2147483651
        HKU                                  = 2147483651
        HKEY_CURRENT_CONFIG                  = 2147483653
        HKCC                                 = 2147483653
        HKEY_DYN_DATA                        = 2147483654
        HKDD                                 = 2147483654
    }
    $TypesDictionary = @{'REG_SZ' = 'SetStringValue'
        'REG_EXPAND_SZ'           = 'SetExpandedStringValue'
        'REG_BINARY'              = 'SetBinaryValue'
        'REG_DWORD'               = 'SetDWORDValue'
        'REG_MULTI_SZ'            = 'SetMultiStringValue'
        'REG_QWORD'               = 'SetQWORDValue'
    }
    $MethodName = $TypesDictionary["$($Type)"]
    $Arguments = foreach ($_ in $RootKeyDictionary.Keys) {
        if ($RegistryPath.StartsWith($_, [System.StringComparison]::CurrentCultureIgnoreCase)) {
            $RootKey = [uint32] $RootKeyDictionary[$_]
            $RegistryValue = @{hDefKey = [uint32] $RootKeyDictionary[$_]
                sSubKeyName            = $RegistryPath.substring($_.Length + 1)
                sValueName             = $Key
            }
            if ($Type -in ('REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ')) { $RegistryValue['sValue'] = [string] $Value } elseif ($Type -in ('REG_DWORD', 'REG_QWORD')) { $RegistryValue['uValue'] = [uint32] $Value } elseif ($Type -in ('REG_BINARY')) { $RegistryValue['uValue'] = [uint8] $Value }
            $RegistryValue
            break
        }
    }
    foreach ($Computer in $ComputersSplit[0]) {
        try {
            $ReturnValues = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -ErrorAction Stop -Verbose:$false
            if ($ReturnValues.ReturnValue -ne 0) { Write-Warning "Set-PSRegistry - Setting registry to $RegistryPath on $Computer may have failed. Please verify." }
        } catch { Write-Warning "Set-PSRegistry - Setting registry to $RegistryPath on $Computer have failed. Error: $($_.Exception.Message)" }
    }
    foreach ($Computer in $ComputersSplit[1]) {
        try {
            $ReturnValues = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -ComputerName $Computer -ErrorAction Stop -Verbose:$false
            if ($ReturnValues.ReturnValue -ne 0) { Write-Warning "Set-PSRegistry - Setting registry to $RegistryPath on $Computer may have failed. Please verify." }
        } catch { Write-Warning "Set-PSRegistry - Setting registry to $RegistryPath on $Computer have failed. Error: $($_.Exception.Message)" }
    }
}
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 Write-Color {
    <#
    .SYNOPSIS
        Write-Color is a wrapper around Write-Host.
 
        It provides:
        - Easy manipulation of colors,
        - Logging output to file (log)
        - Nice formatting options out of the box.
 
    .DESCRIPTION
        Author: przemyslaw.klys at evotec.pl
        Project website: https://evotec.xyz/hub/scripts/write-color-ps1/
        Project support: https://github.com/EvotecIT/PSWriteColor
 
        Original idea: Josh (https://stackoverflow.com/users/81769/josh)
 
    .EXAMPLE
    Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                    "followed by red ",
                    "and then we have Magenta... ",
                    "isn't it fun? ",
                    "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                    "followed by red ",
                    "and then we have Magenta... ",
                    "isn't it fun? ",
                    "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1
 
    .EXAMPLE
    Write-Color "1. ", "Option 1" -Color Yellow, Green
    Write-Color "2. ", "Option 2" -Color Yellow, Green
    Write-Color "3. ", "Option 3" -Color Yellow, Green
    Write-Color "4. ", "Option 4" -Color Yellow, Green
    Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1
 
    .EXAMPLE
    Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss"
    Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt"
 
    .EXAMPLE
    # Added in 0.5
    Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow
    wc -t "my text" -c yellow -b green
    wc -text "my text" -c red
 
    .NOTES
        Additional Notes:
        - TimeFormat https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
    #>

    [alias('Write-Colour')]
    [CmdletBinding()]
    param ([alias ('T')] [String[]]$Text,
        [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White,
        [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null,
        [alias ('Indent')][int] $StartTab = 0,
        [int] $LinesBefore = 0,
        [int] $LinesAfter = 0,
        [int] $StartSpaces = 0,
        [alias ('L')] [string] $LogFile = '',
        [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss',
        [alias ('LogTimeStamp')][bool] $LogTime = $true,
        [int] $LogRetry = 2,
        [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode',
        [switch] $ShowTime,
        [switch] $NoNewLine)
    $DefaultColor = $Color[0]
    if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) {
        Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated."
        return
    }
    if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host -Object "`n" -NoNewline } }
    if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewline } }
    if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewline } }
    if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline }
    if ($Text.Count -ne 0) {
        if ($Color.Count -ge $Text.Count) { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } } else { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } } } else {
            if ($null -eq $BackGroundColor) {
                for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline }
                for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline }
            } else {
                for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline }
                for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline }
            }
        }
    }
    if ($NoNewLine -eq $true) { Write-Host -NoNewline } else { Write-Host }
    if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } }
    if ($Text.Count -and $LogFile) {
        $TextToFile = ""
        for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] }
        $Saved = $false
        $Retry = 0
        Do {
            $Retry++
            try {
                if ($LogTime) { "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } else { "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false }
                $Saved = $true
            } catch { if ($Saved -eq $false -and $Retry -eq $LogRetry) { $PSCmdlet.WriteError($_) } else { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)" } }
        } Until ($Saved -eq $true -or $Retry -ge $LogRetry)
    }
}
function Convert-GenericRightsToFileSystemRights {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER OriginalRights
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
 
    .LINK
    Improved https://blog.cjwdev.co.uk/2011/06/28/permissions-not-included-in-net-accessrule-filesystemrights-enum/
 
    #>

    [cmdletBinding()]
    param([System.Security.AccessControl.FileSystemRights] $OriginalRights)
    Begin {
        $FileSystemRights = [System.Security.AccessControl.FileSystemRights]
        $GenericRights = @{GENERIC_READ = 0x80000000
            GENERIC_WRITE               = 0x40000000
            GENERIC_EXECUTE             = 0x20000000
            GENERIC_ALL                 = 0x10000000
            FILTER_GENERIC              = 0x0FFFFFFF
        }
        $MappedGenericRights = @{FILE_GENERIC_EXECUTE = $FileSystemRights::ExecuteFile -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::ReadAttributes -bor $FileSystemRights::Synchronize
            FILE_GENERIC_READ                         = $FileSystemRights::ReadAttributes -bor $FileSystemRights::ReadData -bor $FileSystemRights::ReadExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize
            FILE_GENERIC_WRITE                        = $FileSystemRights::AppendData -bor $FileSystemRights::WriteAttributes -bor $FileSystemRights::WriteData -bor $FileSystemRights::WriteExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize
            FILE_GENERIC_ALL                          = $FileSystemRights::FullControl
        }
    }
    Process {
        $MappedRights = [System.Security.AccessControl.FileSystemRights]::new()
        if ($OriginalRights -band $GenericRights.GENERIC_EXECUTE) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_EXECUTE }
        if ($OriginalRights -band $GenericRights.GENERIC_READ) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_READ }
        if ($OriginalRights -band $GenericRights.GENERIC_WRITE) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_WRITE }
        if ($OriginalRights -band $GenericRights.GENERIC_ALL) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_ALL }
        (($OriginalRights -bAND $GenericRights.FILTER_GENERIC) -bOR $MappedRights) -as $FileSystemRights
    }
    End {}
}
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 Get-ComputerSplit {
    [CmdletBinding()]
    param([string[]] $ComputerName)
    if ($null -eq $ComputerName) { $ComputerName = $Env:COMPUTERNAME }
    try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Env:COMPUTERNAME }
    $ComputersLocal = $null
    [Array] $Computers = foreach ($Computer in $ComputerName) {
        if ($Computer -eq '' -or $null -eq $Computer) { $Computer = $Env:COMPUTERNAME }
        if ($Computer -ne $Env:COMPUTERNAME -and $Computer -ne $LocalComputerDNSName) { $Computer } else { $ComputersLocal = $Computer }
    }
    , @($ComputersLocal, $Computers)
}
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
}
$Script:ConfigurationBitLocker = [ordered] @{Name = 'Bitlocker Summary'
    Enabled                                       = $true
    Execute                                       = { Get-WinADBitlockerLapsSummary -BitlockerOnly }
    Processing                                    = {}
    Summary                                       = {}
    Variables                                     = @{}
    Solution                                      = { if ($Script:Reporting['BitLocker']['Data']) {
            New-HTMLChart { New-ChartLegend -LegendPosition bottom -HorizontalAlign center -Color Red, Blue, Yellow
                New-ChartTheme -Palette palette5
                foreach ($Object in $DataTable) { New-ChartRadial -Name $Object.Name -Value $Object.Money } }
            New-HTMLTable -DataTable $Script:Reporting['BitLocker']['Data'] -Filtering -SearchBuilder { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor BlizzardBlue
                New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 60 -BackgroundColor Alizarin -HighlightHeaders LastLogonDays, LastLogonDate -FailBackgroundColor LimeGreen
                New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator ge -Value 0 -BackgroundColor LimeGreen -HighlightHeaders PasswordLastSet, PasswordLastChangedDays
                New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 300 -BackgroundColor Orange -HighlightHeaders PasswordLastSet, PasswordLastChangedDays
                New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 360 -BackgroundColor Alizarin -HighlightHeaders PasswordLastSet, PasswordLastChangedDays }
        } }
}
$Script:ConfigurationLAPS = [ordered] @{Name = 'LAPS Summary'
    Enabled                                  = $true
    Execute                                  = { Get-WinADBitlockerLapsSummary -LapsOnly }
    Processing                               = {}
    Summary                                  = {}
    Variables                                = @{}
    Solution                                 = { if ($Script:Reporting['LAPS']['Data']) {
            New-HTMLChart { New-ChartLegend -LegendPosition bottom -HorizontalAlign center -Color Red, Blue, Yellow
                New-ChartTheme -Palette palette5
                foreach ($Object in $DataTable) { New-ChartRadial -Name $Object.Name -Value $Object.Money }
                New-ChartEvent -DataTableID 'NewIDtoSearchInChart' -ColumnID 0 }
            New-HTMLTable -DataTable $Script:Reporting['LAPS']['Data'] -Filtering -SearchBuilder { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor BlizzardBlue
                New-HTMLTableCondition -Name 'LapsExpirationDays' -ComparisonType number -Operator lt -Value 0 -BackgroundColor BurntOrange -HighlightHeaders LapsExpirationDays, LapsExpirationTime -FailBackgroundColor LimeGreen
                New-HTMLTableCondition -Name 'Laps' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor Alizarin
                New-HTMLTableCondition -Name 'Laps' -ComparisonType string -Operator eq -Value $false -BackgroundColor Alizarin -HighlightHeaders LapsExpirationDays, LapsExpirationTime
                New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 60 -BackgroundColor Alizarin -HighlightHeaders LastLogonDays, LastLogonDate -FailBackgroundColor LimeGreen
                New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator ge -Value 0 -BackgroundColor LimeGreen -HighlightHeaders PasswordLastSet, PasswordLastChangedDays
                New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 300 -BackgroundColor Orange -HighlightHeaders PasswordLastSet, PasswordLastChangedDays
                New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 360 -BackgroundColor Alizarin -HighlightHeaders PasswordLastSet, PasswordLastChangedDays }
        } }
}
$Script:ConfigurationLAPSAndBitlocker = [ordered] @{Name = 'LAPS and BITLOCKER'
    Enabled                                              = $true
    Execute                                              = { Get-WinADBitlockerLapsSummary }
    Processing                                           = {}
    Summary                                              = {}
    Variables                                            = @{}
    Solution                                             = { if ($Script:Reporting['LapsAndBitLocker']['Data']) {
            New-HTMLChart { New-ChartLegend -LegendPosition bottom -HorizontalAlign center -Color Red, Blue, Yellow
                New-ChartTheme -Palette palette5
                foreach ($Object in $DataTable) { New-ChartRadial -Name $Object.Name -Value $Object.Money } }
            New-HTMLTable -DataTable $Script:Reporting['LapsAndBitLocker']['Data'] -Filtering -SearchBuilder { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor BlizzardBlue
                New-HTMLTableCondition -Name 'LapsExpirationDays' -ComparisonType number -Operator lt -Value 0 -BackgroundColor BurntOrange -HighlightHeaders LapsExpirationDays, LapsExpirationTime -FailBackgroundColor LimeGreen
                New-HTMLTableCondition -Name 'Laps' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor Alizarin
                New-HTMLTableCondition -Name 'Laps' -ComparisonType string -Operator eq -Value $false -BackgroundColor Alizarin -HighlightHeaders LapsExpirationDays, LapsExpirationTime
                New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 60 -BackgroundColor Alizarin -HighlightHeaders LastLogonDays, LastLogonDate -FailBackgroundColor LimeGreen
                New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator ge -Value 0 -BackgroundColor LimeGreen -HighlightHeaders PasswordLastSet, PasswordLastChangedDays
                New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 300 -BackgroundColor Orange -HighlightHeaders PasswordLastSet, PasswordLastChangedDays
                New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 360 -BackgroundColor Alizarin -HighlightHeaders PasswordLastSet, PasswordLastChangedDays }
        } }
}
$Script:ConfigurationServiceAccounts = [ordered] @{Name = 'Service Accounts'
    Enabled                                             = $true
    Execute                                             = { Get-WinADServiceAccount -PerDomain }
    Processing                                          = {}
    Summary                                             = {}
    Variables                                           = @{}
    Solution                                            = { if ($Script:Reporting['ServiceAccounts']['Data'] -is [System.Collections.IDictionary]) { New-HTMLTabPanel { foreach ($Domain in $Script:Reporting['ServiceAccounts']['Data'].Keys) { New-HTMLTab -Name $Domain { New-HTMLTable -DataTable $Script:Reporting['ServiceAccounts']['Data'][$Domain] -Filtering {} } } } } }
}
$Script:ShowWinADComputer = [ordered] @{Name = 'All Computers'
    Enabled                                  = $true
    Execute                                  = { Get-WinADComputers -PerDomain }
    Processing                               = {}
    Summary                                  = {}
    Variables                                = @{}
    Solution                                 = { if ($Script:Reporting['Computers']['Data'] -is [System.Collections.IDictionary]) { New-HTMLTabPanel { foreach ($Domain in $Script:Reporting['Computers']['Data'].Keys) { New-HTMLTab -Name $Domain { New-HTMLTable -DataTable $Script:Reporting['Computers']['Data'][$Domain] -Filtering {} } } } } }
}
$Script:ShowWinADUser = [ordered] @{Name = 'All Users'
    Enabled                              = $true
    Execute                              = { Get-WinADUsers -PerDomain }
    Processing                           = {}
    Variables                            = @{}
    Summary                              = {}
    Solution                             = { if ($Script:Reporting['Users']['Data'] -is [System.Collections.IDictionary]) { New-HTMLTabPanel -Orientation horizontal { foreach ($Domain in $Script:Reporting['Users']['Data'].Keys) { New-HTMLTab -Name $Domain { New-HTMLTable -DataTable $Script:Reporting['Users']['Data'][$Domain] -Filtering {} } } } } }
}
function Convert-BinaryToIP {
    [cmdletBinding()]
    param([string] $Binary)
    $Binary = $Binary -replace '\s+'
    if ($Binary.Length % 8) {
        Write-Warning -Message "Convert-BinaryToIP - Binary string '$Binary' is not evenly divisible by 8."
        return $Null
    }
    [int] $NumberOfBytes = $Binary.Length / 8
    $Bytes = @(foreach ($i in 0..($NumberOfBytes - 1)) {
            try { [System.Convert]::ToByte($Binary.Substring(($i * 8), 8), 2) } catch {
                Write-Warning -Message "Convert-BinaryToIP - Error converting '$Binary' to bytes. `$i was $i."
                return $Null
            }
        })
    return $Bytes -join '.'
}
function Convert-IPToBinary {
    [cmdletBinding()]
    param([string] $IP)
    $IPv4Regex = '(?:(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)\.){3}(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)'
    $IP = $IP.Trim()
    if ($IP -match "\A${IPv4Regex}\z") {
        try { return ($IP.Split('.') | ForEach-Object { [System.Convert]::ToString([byte] $_, 2).PadLeft(8, '0') }) -join '' } catch {
            Write-Warning -Message "Convert-IPToBinary - Error converting '$IP' to a binary string: $_"
            return $Null
        }
    } else {
        Write-Warning -Message "Convert-IPToBinary - Invalid IP detected: '$IP'. Conversion failed."
        return $Null
    }
}
function Convert-TrustForestTrustInfo {
    [CmdletBinding()]
    param([byte[]] $msDSTrustForestTrustInfo)
    $Flags = [ordered] @{'0'     = 'Enabled'
        'LsaTlnDisabledNew'      = 'Not yet enabled'
        'LsaTlnDisabledAdmin'    = 'Disabled by administrator'
        'LsaTlnDisabledConflict' = 'Disabled due to a conflict with another trusted domain'
        'LsaSidDisabledAdmin'    = 'Disabled for SID, NetBIOS, and DNS name–based matches by the administrator'
        'LsaSidDisabledConflict' = 'Disabled for SID, NetBIOS, and DNS name–based matches due to a SID or DNS name–based conflict with another trusted domain'
        'LsaNBDisabledAdmin'     = 'Disabled for NetBIOS name–based matches by the administrator'
        'LsaNBDisabledConflict'  = 'Disabled for NetBIOS name–based matches due to a NetBIOS domain name conflict with another trusted domain'
    }
    if ($msDSTrustForestTrustInfo) {
        $Read = Get-ForestTrustInfo -Byte $msDSTrustForestTrustInfo
        $ForestTrustDomainInfo = [ordered]@{}
        [Array] $Records = foreach ($Record in $Read.Records) {
            if ($Record.RecordType -ne 'ForestTrustDomainInfo') {
                if ($Record.RecordType -eq 'ForestTrustTopLevelName') { $Type = 'Included' } else { $Type = 'Excluded' }
                [PSCustomObject] @{DnsName = $null
                    NetbiosName            = $null
                    Sid                    = $null
                    Type                   = $Type
                    Suffix                 = $Record.ForestTrustData
                    Status                 = $Flags["$($Record.Flags)"]
                    StatusFlag             = $Record.Flags
                    WhenCreated            = $Record.Timestamp
                }
            } else {
                $ForestTrustDomainInfo['DnsName'] = $Record.ForestTrustData.DnsName
                $ForestTrustDomainInfo['NetbiosName'] = $Record.ForestTrustData.NetbiosName
                $ForestTrustDomainInfo['Sid'] = $Record.ForestTrustData.Sid
            }
        }
        foreach ($Record in $Records) {
            $Record.DnsName = $ForestTrustDomainInfo['DnsName']
            $Record.NetbiosName = $ForestTrustDomainInfo['NetbiosName']
            $Record.Sid = $ForestTrustDomainInfo['Sid']
        }
        $Records
    }
}
function ConvertTo-ComputerFQDN {
    [cmdletBinding()]
    param([string] $Computer)
    $IPAddressCheck = [System.Net.IPAddress]::TryParse($Computer, [ref][ipaddress]::Any)
    $IPAddressMatch = $Computer -match '^(\d+\.){3}\d+$'
    if ($IPAddressCheck -and $IPAddressMatch) {
        [Array] $ADServerFQDN = (Resolve-DnsName -Name $Computer -ErrorAction SilentlyContinue -Type PTR -Verbose:$false)
        if ($ADServerFQDN.Count -gt 0) { $ServerName = $ADServerFQDN[0].NameHost } else { $ServerName = $Computer }
    } else {
        [Array] $ADServerFQDN = (Resolve-DnsName -Name $Computer -ErrorAction SilentlyContinue -Type A -Verbose:$false)
        if ($ADServerFQDN.Count -gt 0) { $ServerName = $ADServerFQDN[0].Name } else { $ServerName = $Computer }
    }
    $ServerName
}
function ConvertTo-Date {
    [cmdletBinding()]
    Param ([Parameter(ValueFromPipeline, Mandatory)]$AccountExpires)
    process {
        $lngValue = $AccountExpires
        if (($lngValue -eq 0) -or ($lngValue -gt [DateTime]::MaxValue.Ticks)) { $AccountExpirationDate = $null } else {
            $Date = [DateTime]$lngValue
            $AccountExpirationDate = $Date.AddYears(1600).ToLocalTime()
        }
        $AccountExpirationDate
    }
}
function Get-ADConfigurationPermission {
    [cmdletBinding()]
    param([System.Collections.IDictionary]$ADObjectSplat,
        [string] $ObjectType,
        [switch] $FilterOut,
        [switch] $Owner)
    try { $Objects = Get-ADObject @ADObjectSplat -ErrorAction Stop } catch { Write-Warning "Get-ADConfigurationPermission - LDAP Filter: $($ADObjectSplat.LDAPFilter), SearchBase: $($ADObjectSplat.SearchBase)), Error: $($_.Exception.Message)" }
    foreach ($O in $Objects) {
        if ($FilterOut) {
            if ($ObjectType -eq 'site') {
                if ($O.DistinguishedName -like '*CN=Subnets,CN=Sites,CN=Configuration*') { continue }
                if ($O.DistinguishedName -like '*CN=Inter-Site Transports,CN=Sites,CN=Configuration*') { continue }
            }
        }
        if ($Owner) {
            Write-Verbose "Get-ADConfigurationPermission - Getting Owner from $($O.DistinguishedName)"
            $OwnerACL = Get-ADACLOwner -ADObject $O.DistinguishedName -Resolve
            [PSCustomObject] @{Name = $O.Name
                CanonicalName       = $O.CanonicalName
                ObjectType          = $ObjectType
                ObjectClass         = $O.ObjectClass
                Owner               = $OwnerACL.Owner
                OwnerName           = $OwnerACL.OwnerName
                OwnerType           = $OwnerACL.OwnerType
                WhenCreated         = $O.WhenCreated
                WhenChanged         = $O.WhenChanged
                DistinguishedName   = $O.DistinguishedName
            }
        } else { Get-ADACL -ADObject $O.DistinguishedName -ResolveTypes }
    }
}
function Get-ADSubnet {
    [cmdletBinding()]
    param([Array] $Subnets,
        [switch] $AsHashTable)
    foreach ($Subnet in $Subnets) {
        if ($Subnet.SiteObject) { $SiteObject = ConvertFrom-DistinguishedName -DistinguishedName $Subnet.SiteObject } else { $SiteObject = '' }
        $Addr = $Subnet.Name.Split('/')
        $Address = [PSCustomObject] @{IP = $Addr[0]
            NetworkLength                = $Addr[1]
        }
        try { $IPAddress = ([IPAddress] $Address.IP) } catch { Write-Warning "Get-ADSubnet - Conversion to IP failed. Error: $($_.Exception.Message)" }
        if ($IPAddress.AddressFamily -eq 'InterNetwork') {
            $AddressRange = Get-IPAddressRangeInformation -CIDRObject $Address
            $MaskBits = ([int](($Subnet.Name -split "/")[1]))
            if ($AsHashTable) {
                [ordered] @{Name = $Subnet.Name
                    Type         = 'IPv4'
                    SiteName     = $SiteObject
                    SiteStatus   = if ($SiteObject) { $true } else { $false }
                    OverLap      = $null
                    OverLapList  = $null
                    Subnet       = ([IPAddress](($Subnet.Name -split "/")[0]))
                    MaskBits     = ([int](($Subnet.Name -split "/")[1]))
                    SubnetMask   = ([IPAddress]"$([system.convert]::ToInt64(("1"*$MaskBits).PadRight(32,"0"),2))")
                    TotalHosts   = $AddressRange.TotalHosts
                    UsableHosts  = $AddressRange.UsableHosts
                    HostMin      = $AddressRange.HostMin
                    HostMax      = $AddressRange.HostMax
                    Broadcast    = $AddressRange.Broadcast
                }
            } else {
                [PSCustomObject] @{Name = $Subnet.Name
                    Type                = 'IPv4'
                    SiteName            = $SiteObject
                    SiteStatus          = if ($SiteObject) { $true } else { $false }
                    Subnet              = ([IPAddress](($Subnet.Name -split "/")[0]))
                    MaskBits            = ([int](($Subnet.Name -split "/")[1]))
                    SubnetMask          = ([IPAddress]"$([system.convert]::ToInt64(("1"*$MaskBits).PadRight(32,"0"),2))")
                    TotalHosts          = $AddressRange.TotalHosts
                    UsableHosts         = $AddressRange.UsableHosts
                    HostMin             = $AddressRange.HostMin
                    HostMax             = $AddressRange.HostMax
                    Broadcast           = $AddressRange.Broadcast
                }
            }
        } else {
            $AddressRange = $null
            if ($AsHashTable) {
                [ordered] @{Name = $Subnet.Name
                    Type         = 'IPv6'
                    SiteName     = $SiteObject
                    SiteStatus   = if ($SiteObject) { $true } else { $false }
                    OverLap      = $null
                    OverLapList  = $null
                    Subnet       = ([IPAddress](($Subnet.Name -split "/")[0]))
                    MaskBits     = ([int](($Subnet.Name -split "/")[1]))
                    SubnetMask   = $null
                    TotalHosts   = $AddressRange.TotalHosts
                    UsableHosts  = $AddressRange.UsableHosts
                    HostMin      = $AddressRange.HostMin
                    HostMax      = $AddressRange.HostMax
                    Broadcast    = $AddressRange.Broadcast
                }
            } else {
                [PSCustomObject] @{Name = $Subnet.Name
                    Type                = 'IPv6'
                    SiteName            = $SiteObject
                    SiteStatus          = if ($SiteObject) { $true } else { $false }
                    Subnet              = ([IPAddress](($Subnet.Name -split "/")[0]))
                    MaskBits            = ([int](($Subnet.Name -split "/")[1]))
                    SubnetMask          = $null
                    TotalHosts          = $AddressRange.TotalHosts
                    UsableHosts         = $AddressRange.UsableHosts
                    HostMin             = $AddressRange.HostMin
                    HostMax             = $AddressRange.HostMax
                    Broadcast           = $AddressRange.Broadcast
                }
            }
        }
    }
}
function Get-ForestTrustInfo {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Byte
    An array of bytes which describes the forest trust information.
 
    .EXAMPLE
    An example
 
    .NOTES
    Author: Chris Dent
    #>

    [CmdletBinding()]
    param ([Parameter(Mandatory)][byte[]]$Byte)
    $reader = [System.IO.BinaryReader][System.IO.MemoryStream]$Byte
    $trustInfo = [PSCustomObject]@{Version = $reader.ReadUInt32()
        RecordCount                        = $reader.ReadUInt32()
        Records                            = $null
    }
    $trustInfo.Records = for ($i = 0; $i -lt $trustInfo.RecordCount; $i++) { Get-ForestTrustRecord -BinaryReader $reader }
    $trustInfo
}
function Get-ForestTrustRecord {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER BinaryReader
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    Author: Chris Dent
    #>

    [CmdletBinding()]
    param ([Parameter(Mandatory)][System.IO.BinaryReader]$BinaryReader)
    [Flags()]
    enum TrustFlags {
        LsaTlnDisabledNew = 0x1
        LsaTlnDisabledAdmin = 0x2
        LsaTlnDisabledConflict = 0x4
    }
    [Flags()]
    enum ForestTrustFlags {
        LsaSidDisabledAdmin = 0x1
        LsaSidDisabledConflict = 0x2
        LsaNBDisabledAdmin = 0x4
        LsaNBDisabledConflict = 0x8
    }
    enum RecordType {
        ForestTrustTopLevelName
        ForestTrustTopLevelNameEx
        ForestTrustDomainInfo
    }
    $record = [PSCustomObject]@{RecordLength = $BinaryReader.ReadUInt32()
        Flags                                = $BinaryReader.ReadUInt32()
        Timestamp                            = $BinaryReader.ReadUInt32(), $BinaryReader.ReadUInt32()
        RecordType                           = $BinaryReader.ReadByte() -as [RecordType]
        ForestTrustData                      = $null
    }
    $record.Timestamp = [DateTime]::FromFileTimeUtc(($record.Timestamp[0] -as [UInt64] -shl 32) + $record.Timestamp[1])
    $record.Flags = switch ($record.RecordType) {
        ([RecordType]::ForestTrustDomainInfo) { $record.Flags -as [ForestTrustFlags] }
        default { $record.Flags -as [TrustFlags] }
    }
    if ($record.RecordLength -gt 11) {
        switch ($record.RecordType) {
            ([RecordType]::ForestTrustDomainInfo) {
                $record.ForestTrustData = [PSCustomObject]@{Sid = $null
                    DnsName                                     = $null
                    NetbiosName                                 = $null
                }
                $sidLength = $BinaryReader.ReadUInt32()
                if ($sidLength -gt 0) {
                    $record.ForestTrustData.Sid = [System.Security.Principal.SecurityIdentifier]::new($BinaryReader.ReadBytes($sidLength),
                        0)
                }
                $dnsNameLen = $BinaryReader.ReadUInt32()
                if ($dnsNameLen -gt 0) { $record.ForestTrustData.DnsName = [string]::new($BinaryReader.ReadBytes($dnsNameLen) -as [char[]]) }
                $NetbiosNameLen = $BinaryReader.ReadUInt32()
                if ($NetbiosNameLen -gt 0) { $record.ForestTrustData.NetbiosName = [string]::new($BinaryReader.ReadBytes($NetbiosNameLen) -as [char[]]) }
            }
            default {
                $nameLength = $BinaryReader.ReadUInt32()
                if ($nameLength -gt 0) { $record.ForestTrustData = [String]::new($BinaryReader.ReadBytes($nameLength) -as [char[]]) }
            }
        }
    }
    $record
}
function Get-GitHubVersion {
    [cmdletBinding()]
    param([Parameter(Mandatory)][string] $Cmdlet,
        [Parameter(Mandatory)][string] $RepositoryOwner,
        [Parameter(Mandatory)][string] $RepositoryName)
    $App = Get-Command -Name $Cmdlet -ErrorAction SilentlyContinue
    if ($App) {
        [Array] $GitHubReleases = (Get-GitHubLatestRelease -Url "https://api.github.com/repos/$RepositoryOwner/$RepositoryName/releases" -Verbose:$false)
        $LatestVersion = $GitHubReleases[0]
        if (-not $LatestVersion.Errors) { if ($App.Version -eq $LatestVersion.Version) { "Current/Latest: $($LatestVersion.Version) at $($LatestVersion.PublishDate)" } elseif ($App.Version -lt $LatestVersion.Version) { "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Update?" } elseif ($App.Version -gt $LatestVersion.Version) { "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Lucky you!" } } else { "Current: $($App.Version)" }
    }
}
function Get-IPAddressRangeInformation {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER CIDRObject
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
 
    #>

    [cmdletBinding()]
    param([psobject] $CIDRObject)
    $IPv4Regex = '(?:(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)\.){3}(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)'
    $o = [ordered] @{}
    $o.IP = [string] $CIDRObject.IP
    $o.BinaryIP = Convert-IPToBinary $o.IP
    if (-not $o.BinaryIP) { return }
    $o.NetworkLength = [int32] $CIDRObject.NetworkLength
    $o.SubnetMask = Convert-BinaryToIP ('1' * $o.NetworkLength).PadRight(32, '0')
    $o.BinarySubnetMask = ('1' * $o.NetworkLength).PadRight(32, '0')
    $o.BinaryNetworkAddress = $o.BinaryIP.SubString(0, $o.NetworkLength).PadRight(32, '0')
    if ($Contains) {
        if ($Contains -match "\A${IPv4Regex}\z") { return Test-IPIsInNetwork $Contains $o.BinaryNetworkAddress $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1') } else {
            Write-Error "Get-IPAddressRangeInformation - Invalid IPv4 address specified with -Contains"
            return
        }
    }
    $o.NetworkAddress = Convert-BinaryToIP $o.BinaryNetworkAddress
    if ($o.NetworkLength -eq 32 -or $o.NetworkLength -eq 31) { $o.HostMin = $o.IP } else { $o.HostMin = Convert-BinaryToIP ([System.Convert]::ToString(([System.Convert]::ToInt64($o.BinaryNetworkAddress, 2) + 1), 2)).PadLeft(32, '0') }
    [string] $BinaryBroadcastIP = $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1')
    $o.BinaryBroadcast = $BinaryBroadcastIP
    [int64] $DecimalHostMax = [System.Convert]::ToInt64($BinaryBroadcastIP, 2) - 1
    [string] $BinaryHostMax = [System.Convert]::ToString($DecimalHostMax, 2).PadLeft(32, '0')
    $o.HostMax = Convert-BinaryToIP $BinaryHostMax
    $o.TotalHosts = [int64][System.Convert]::ToString(([System.Convert]::ToInt64($BinaryBroadcastIP, 2) - [System.Convert]::ToInt64($o.BinaryNetworkAddress, 2) + 1))
    $o.UsableHosts = $o.TotalHosts - 2
    if ($o.NetworkLength -eq 32) {
        $o.Broadcast = $Null
        $o.UsableHosts = [int64] 1
        $o.TotalHosts = [int64] 1
        $o.HostMax = $o.IP
    } elseif ($o.NetworkLength -eq 31) {
        $o.Broadcast = $Null
        $o.UsableHosts = [int64] 2
        $o.TotalHosts = [int64] 2
        [int64] $DecimalHostMax2 = [System.Convert]::ToInt64($BinaryBroadcastIP, 2)
        [string] $BinaryHostMax2 = [System.Convert]::ToString($DecimalHostMax2, 2).PadLeft(32, '0')
        $o.HostMax = Convert-BinaryToIP $BinaryHostMax2
    } elseif ($o.NetworkLength -eq 30) {
        $o.UsableHosts = [int64] 2
        $o.TotalHosts = [int64] 4
        $o.Broadcast = Convert-BinaryToIP $BinaryBroadcastIP
    } else { $o.Broadcast = Convert-BinaryToIP $BinaryBroadcastIP }
    if ($Enumerate) {
        $IPRange = @(Get-IPRange $o.BinaryNetworkAddress $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1'))
        if ((31, 32) -notcontains $o.NetworkLength) {
            $IPRange = $IPRange[1..($IPRange.Count - 1)]
            $IPRange = $IPRange[0..($IPRange.Count - 2)]
        }
        $o.IPEnumerated = $IPRange
    } else { $o.IPEnumerated = @() }
    [PSCustomObject]$o
}
function Get-IPRange {
    [cmdletBinding()]
    param([string] $StartBinary,
        [string] $EndBinary)
    [int64] $StartInt = [System.Convert]::ToInt64($StartBinary, 2)
    [int64] $EndInt = [System.Convert]::ToInt64($EndBinary, 2)
    for ($BinaryIP = $StartInt; $BinaryIP -le $EndInt; $BinaryIP++) { Convert-BinaryToIP ([System.Convert]::ToString($BinaryIP, 2).PadLeft(32, '0')) }
}
function Get-WinADCache {
    [alias('Get-ADCache')]
    [cmdletbinding()]
    param([switch] $ByDN,
        [switch] $ByNetBiosName)
    $ForestObjectsCache = [ordered] @{}
    $Forest = Get-ADForest
    foreach ($Domain in $Forest.Domains) {
        $Server = Get-ADDomainController -Discover -DomainName $Domain
        try {
            $DomainInformation = Get-ADDomain -Server $Server.Hostname[0]
            $Users = Get-ADUser -Filter * -Server $Server.Hostname[0]
            $Groups = Get-ADGroup -Filter * -Server $Server.Hostname[0]
            $Computers = Get-ADComputer -Filter * -Server $Server.Hostname[0]
        } catch {
            Write-Warning "Get-ADCache - Can't process domain $Domain - $($_.Exception.Message)"
            continue
        }
        if ($ByDN) {
            foreach ($_ in $Users) { $ForestObjectsCache["$($_.DistinguishedName)"] = $_ }
            foreach ($_ in $Groups) { $ForestObjectsCache["$($_.DistinguishedName)"] = $_ }
            foreach ($_ in $Computers) { $ForestObjectsCache["$($_.DistinguishedName)"] = $_ }
        } elseif ($ByNetBiosName) {
            foreach ($_ in $Users) {
                $Identity = -join ($DomainInformation.NetBIOSName, '\', $($_.SamAccountName))
                $ForestObjectsCache["$Identity"] = $_
            }
            foreach ($_ in $Groups) {
                $Identity = -join ($DomainInformation.NetBIOSName, '\', $($_.SamAccountName))
                $ForestObjectsCache["$Identity"] = $_
            }
            foreach ($_ in $Computers) {
                $Identity = -join ($DomainInformation.NetBIOSName, '\', $($_.SamAccountName))
                $ForestObjectsCache["$Identity"] = $_
            }
        } else { Write-Warning "Get-ADCache - No choice made." }
    }
    $ForestObjectsCache
}
function Get-WinADDomainOrganizationalUnitsACLExtended {
    [cmdletbinding()]
    param([Array] $DomainOrganizationalUnitsClean,
        [string] $Domain = $Env:USERDNSDOMAIN,
        [string] $NetBiosName,
        [string] $RootDomainNamingContext,
        [System.Collections.IDictionary] $GUID,
        [System.Collections.IDictionary] $ForestObjectsCache,
        $Server)
    if (-not $GUID) { $GUID = @{} }
    if (-not $ForestObjectsCache) { $ForestObjectsCache = @{} }
    $OUs = @(foreach ($OU in $DomainOrganizationalUnitsClean) { @{Name = 'Organizational Unit'; Value = $OU.DistinguishedName } })
    if ($Server) { $null = New-PSDrive -Name $NetBiosName -Root '' -PSProvider ActiveDirectory -Server $Server } else { $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) {
            if ($ACL.IdentityReference -like '*\*') {
                $TemporaryIdentity = $ForestObjectsCache["$($ACL.IdentityReference)"]
                $IdentityReferenceType = $TemporaryIdentity.ObjectClass
                $IdentityReference = $ACL.IdentityReference.Value
            } elseif ($ACL.IdentityReference -like '*-*-*-*') {
                $ConvertedSID = ConvertFrom-SID -SID $ACL.IdentityReference
                $TemporaryIdentity = $ForestObjectsCache["$($ConvertedSID.Name)"]
                $IdentityReferenceType = $TemporaryIdentity.ObjectClass
                $IdentityReference = $ConvertedSID.Name
            } else {
                $IdentityReference = $ACL.IdentityReference
                $IdentityReferenceType = 'Unknown'
            }
            [PSCustomObject] @{'Distinguished Name' = $OU.Value
                'Type'                              = $OU.Name
                'AccessControlType'                 = $ACL.AccessControlType
                'Rights'                            = $Global:Rights["$($ACL.ActiveDirectoryRights)"]["$($ACL.ObjectFlags)"]
                'ObjectType Name'                   = $GUID["$($ACL.objectType)"]
                'Inherited ObjectType Name'         = $GUID["$($ACL.inheritedObjectType)"]
                'ActiveDirectoryRights'             = $ACL.ActiveDirectoryRights
                'InheritanceType'                   = $ACL.InheritanceType
                'ObjectFlags'                       = $ACL.ObjectFlags
                'IdentityReference'                 = $IdentityReference
                'IdentityReferenceType'             = $IdentityReferenceType
                'IsInherited'                       = $ACL.IsInherited
                'InheritanceFlags'                  = $ACL.InheritanceFlags
                'PropagationFlags'                  = $ACL.PropagationFlags
            }
        }
    }
}
function Get-WinADTrustObject {
    [cmdletBinding()]
    param([Parameter(Mandatory, Position = 0)][alias('Domain')][string] $Identity,
        [switch] $AsHashTable)
    $Summary = [ordered] @{}
    $TrustType = @{CrossLink = 'The trust relationship is a shortcut between two domains that exists to optimize the authentication processing between two domains that are in separate domain trees.'
        External             = 'The trust relationship is with a domain outside of the current forest.'
        Forest               = 'The trust relationship is between two forest root domains in separate Windows Server 2003 forests.'
        Kerberos             = 'The trusted domain is an MIT Kerberos realm.'
        ParentChild          = 'The trust relationship is between a parent and a child domain.'
        TreeRoot             = 'One of the domains in the trust relationship is a tree root.'
        Unknown              = 'The trust is a non-specific type.'
    }
    $TrustDirection = @{Bidirectional = 'Each domain or forest has access to the resources of the other domain or forest.'
        Inbound                       = 'This is a trusting domain or forest. The other domain or forest has access to the resources of this domain or forest. This domain or forest does not have access to resources that belong to the other domain or forest.'
        Outbound                      = 'This is a trusted domain or forest. This domain or forest has access to resources of the other domain or forest. The other domain or forest does not have access to the resources of this domain or forest.'
    }
    if ($Identity -contains 'DC=') {
        $DomainName = "LDAP://$Domain"
        $TrustSource = ConvertFrom-DistinguishedName -DistinguishedName $DomainName -ToDomainCN
    } else {
        $DomainDN = ConvertTo-DistinguishedName -CanonicalName $Identity -ToDomain
        $DomainName = "LDAP://$DomainDN"
        $TrustSource = $Identity
    }
    $searcher = [adsisearcher]'(objectClass=trustedDomain)'
    $searcher.SearchRoot = [adsi] $DomainName
    $Trusts = $searcher.FindAll()
    foreach ($Trust in $Trusts) {
        $TrustD = [System.DirectoryServices.ActiveDirectory.TrustDirection] $Trust.properties.trustdirection[0]
        $TrustT = [System.DirectoryServices.ActiveDirectory.TrustType] $Trust.properties.trusttype[0]
        if ($Trust.properties.'msds-trustforesttrustinfo') { $msDSTrustForestTrustInfo = Convert-TrustForestTrustInfo -msDSTrustForestTrustInfo $Trust.properties.'msds-trustforesttrustinfo'[0] } else { $msDSTrustForestTrustInfo = $null }
        if ($Trust.properties.trustattributes) { $TrustAttributes = Get-ADTrustAttributes -Value ([int] $Trust.properties.trustattributes[0]) } else { $TrustAttributes = $null }
        if ($Trust.properties.securityidentifier) { try { $ObjectSID = [System.Security.Principal.SecurityIdentifier]::new($Trust.properties.securityidentifier[0], 0).Value } catch { $ObjectSID = $null } } else { $ObjectSID = $null }
        $TrustObject = [PSCustomObject] @{TrustSource = $TrustSource
            TrustPartner                              = [string] $Trust.properties.trustpartner
            TrustPartnerNetBios                       = [string] $Trust.properties.flatname
            TrustDirection                            = $TrustD.ToString()
            TrustType                                 = $TrustT.ToString()
            TrustAttributes                           = $TrustAttributes
            TrustDirectionText                        = $TrustDirection[$TrustD.ToString()]
            TrustTypeText                             = $TrustType[$TrustT.ToString()]
            WhenCreated                               = [DateTime] $Trust.properties.whencreated[0]
            WhenChanged                               = [DateTime] $Trust.properties.whenchanged[0]
            ObjectSID                                 = $ObjectSID
            Distinguishedname                         = [string] $Trust.properties.distinguishedname
            IsCriticalSystemObject                    = [bool]::Parse($Trust.properties.iscriticalsystemobject[0])
            ObjectGuid                                = [guid]::new($Trust.properties.objectguid[0])
            ObjectCategory                            = [string] $Trust.properties.objectcategory
            ObjectClass                               = ([array] $Trust.properties.objectclass)[-1]
            UsnCreated                                = [string] $Trust.properties.usncreated
            UsnChanged                                = [string] $Trust.properties.usnchanged
            ShowInAdvancedViewOnly                    = [bool]::Parse($Trust.properties.showinadvancedviewonly)
            TrustPosixOffset                          = [string] $Trust.properties.trustposixoffset
            msDSTrustForestTrustInfo                  = $msDSTrustForestTrustInfo
            msDSSupportedEncryptionTypes              = if ($Trust.properties.'msds-supportedencryptiontypes') { Get-ADEncryptionTypes -Value ([int] $Trust.properties.'msds-supportedencryptiontypes'[0]) } else { $null }
        }
        if ($AsHashTable) { $Summary[$TrustObject.trustpartner] = $TrustObject } else { $TrustObject }
    }
    if ($AsHashTable) { $Summary }
}
$Script:Rights = @{"Self"             = @{"InheritedObjectAceTypePresent" = ""
        "ObjectAceTypePresent"                                            = ""
        "ObjectAceTypePresent, InheritedObjectAceTypePresent"             = ""
        'None'                                                            = ""
    }
    "DeleteChild, DeleteTree, Delete" = @{"InheritedObjectAceTypePresent" = "DeleteChild, DeleteTree, Delete"
        "ObjectAceTypePresent"                                            = "DeleteChild, DeleteTree, Delete"
        "ObjectAceTypePresent, InheritedObjectAceTypePresent"             = "DeleteChild, DeleteTree, Delete"
        'None'                                                            = "DeleteChild, DeleteTree, Delete"
    }
    "GenericRead"                     = @{"InheritedObjectAceTypePresent"         = "Read Permissions,List Contents,Read All Properties,List"
        "ObjectAceTypePresent"                                = "Read Permissions,List Contents,Read All Properties,List"
        "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Read Permissions,List Contents,Read All Properties,List"
        'None'                                                = "Read Permissions,List Contents,Read All Properties,List"
    }
    "CreateChild"                     = @{"InheritedObjectAceTypePresent" = "Create"
        "ObjectAceTypePresent"                                            = "Create"
        "ObjectAceTypePresent, InheritedObjectAceTypePresent"             = "Create"
        'None'                                                            = "Create"
    }
    "DeleteChild"                     = @{"InheritedObjectAceTypePresent"         = "Delete"
        "ObjectAceTypePresent"                                = "Delete"
        "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Delete"
        'None'                                                = "Delete"
    }
    "GenericAll"                      = @{"InheritedObjectAceTypePresent"          = "Full Control"
        "ObjectAceTypePresent"                                = "Full Control"
        "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Full Control"
        'None'                                                = "Full Control"
    }
    "CreateChild, DeleteChild"        = @{"InheritedObjectAceTypePresent" = "Create/Delete"
        "ObjectAceTypePresent"                                            = "Create/Delete"
        "ObjectAceTypePresent, InheritedObjectAceTypePresent"             = "Create/Delete"
        'None'                                                            = "Create/Delete"
    }
    "ReadProperty, WriteProperty"     = @{"InheritedObjectAceTypePresent" = "Read All Properties;Write All Properties"
        "ObjectAceTypePresent"                                            = "Read All Properties;Write All Properties"
        "ObjectAceTypePresent, InheritedObjectAceTypePresent"             = "Read All Properties;Write All Properties"
        'None'                                                            = "Read All Properties;Write All Properties"
    }
    "WriteProperty"                   = @{"InheritedObjectAceTypePresent"       = "Write All Properties"
        "ObjectAceTypePresent"                                = "Write"
        "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Write"
        'None'                                                = "Write All Properties"
    }
    "ReadProperty"                    = @{"InheritedObjectAceTypePresent" = "Read All Properties"
        "ObjectAceTypePresent"                                            = "Read"
        "ObjectAceTypePresent, InheritedObjectAceTypePresent"             = "Read"
        'None'                                                            = "Read All Properties"
    }
}
function New-ADForestDrives {
    [cmdletbinding()]
    param([string] $ForestName,
        [string] $ObjectDN)
    if (-not $Global:ADDrivesMapped) {
        if ($ForestName) { $Forest = Get-ADForest -Identity $ForestName } else { $Forest = Get-ADForest }
        if ($ObjectDN) {
            $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $ObjectDN -ToDC) -replace '=' -replace ','
            if (-not(Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                try {
                    if ($Server) {
                        $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Server.Hostname[0] -Scope Global -WhatIf:$false
                        Write-Verbose "New-ADForestDrives - Mapped drive $Domain / $($Server.Hostname[0])"
                    } else { $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Domain -Scope Global -WhatIf:$false }
                } catch { Write-Warning "New-ADForestDrives - Couldn't map new AD psdrive for $Domain / $($Server.Hostname[0])" }
            }
        } else {
            foreach ($Domain in $Forest.Domains) {
                try {
                    $Server = Get-ADDomainController -Discover -DomainName $Domain -Writable
                    $DomainInformation = Get-ADDomain -Server $Server.Hostname[0]
                } catch {
                    Write-Warning "New-ADForestDrives - Can't process domain $Domain - $($_.Exception.Message)"
                    continue
                }
                $ObjectDN = $DomainInformation.DistinguishedName
                $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $ObjectDN -ToDC) -replace '=' -replace ','
                if (-not(Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                    try {
                        if ($Server) {
                            $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Server.Hostname[0] -Scope Global -WhatIf:$false
                            Write-Verbose "New-ADForestDrives - Mapped drive $Domain / $Server"
                        } else { $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Domain -Scope Global -WhatIf:$false }
                    } catch { Write-Warning "New-ADForestDrives - Couldn't map new AD psdrive for $Domain / $Server $($_.Exception.Message)" }
                }
            }
        }
        $Global:ADDrivesMapped = $true
    }
}
function New-HTMLGroupDiagramDefault {
    [cmdletBinding()]
    param([Array] $ADGroup,
        [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both',
        [switch] $HideComputers,
        [switch] $HideUsers,
        [switch] $HideOther,
        [string] $DataTableID,
        [int] $ColumnID,
        [switch] $Online)
    New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion
        if ($ADGroup) {
            foreach ($ADObject in $ADGroup) {
                $ID = "$($ADObject.DomainName)$($ADObject.Name)"
                $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)"
                if ($ADObject.Type -eq 'User') {
                    if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Default') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user -IconColor LightSteelBlue }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsToEnabled -Dashes
                    }
                } elseif ($ADObject.Type -eq 'Group') {
                    if ($ADObject.Nesting -eq -1) {
                        $BorderColor = 'Red'
                        $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg'
                    } else {
                        $BorderColor = 'Blue'
                        $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg'
                    }
                    $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers)
                    $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers
                    if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user-friends -IconColor VeryLightGrey }
                    New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsToEnabled
                } elseif ($ADObject.Type -eq 'Computer') {
                    if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Default') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsToEnabled -Dashes
                    }
                } else {
                    if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Default') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsToEnabled -Dashes
                    }
                }
            }
        } }
}
function New-HTMLGroupDiagramHierachical {
    [cmdletBinding()]
    param([Array] $ADGroup,
        [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both',
        [switch] $HideComputers,
        [switch] $HideUsers,
        [switch] $HideOther,
        [switch] $Online)
    New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsLayout -HierarchicalEnabled $true
        New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200
        if ($ADGroup) {
            foreach ($ADObject in $ADGroup) {
                [int] $Level = $($ADObject.Nesting) + 1
                $ID = "$($ADObject.DomainName)$($ADObject.Name)$Level"
                [int] $LevelParent = $($ADObject.Nesting)
                $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)$LevelParent"
                [int] $Level = $($ADObject.Nesting) + 1
                if ($ADObject.Type -eq 'User') {
                    if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user -IconColor LightSteelBlue }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsToEnabled -Dashes
                    }
                } elseif ($ADObject.Type -eq 'Group') {
                    if ($ADObject.Nesting -eq -1) {
                        $BorderColor = 'LightGreen'
                        $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg'
                        $IconSolid = 'user-friends'
                    } elseif ($ADObject.CircularIndirect -eq $true -or $ADObject.CircularDirect -eq $true) {
                        $Image = 'https://www.flaticon.com/svg/static/icons/svg/3039/3039356.svg'
                        $BorderColor = 'PaleVioletRed'
                        $IconSolid = 'circle-notch'
                    } else {
                        $BorderColor = 'VeryLightGrey'
                        $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg'
                        $IconSolid = 'users'
                    }
                    $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers)
                    if ($ADObject.CircularIndirect -eq $true -or $ADObject.CircularDirect -eq $true) { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers + [System.Environment]::NewLine + "Circular: $True" } else { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers }
                    if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -Level $Level -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid $IconSolid -IconColor $BorderColor }
                    New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsToEnabled
                } elseif ($ADObject.Type -eq 'Computer') {
                    if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray -Level $Level }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsToEnabled -Dashes
                    }
                } else {
                    if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon -Level $Level }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsToEnabled -Dashes
                    }
                }
            }
        } }
}
function New-HTMLGroupDiagramSummary {
    [cmdletBinding()]
    param([Array] $ADGroup,
        [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both',
        [switch] $HideComputers,
        [switch] $HideUsers,
        [switch] $HideOther,
        [string] $DataTableID,
        [int] $ColumnID,
        [switch] $Online)
    $ConnectionsTracker = @{}
    New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion
        if ($ADGroup) {
            foreach ($ADObject in $ADGroup) {
                $ID = "$($ADObject.DomainName)$($ADObject.Name)"
                $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)"
                if (-not $ConnectionsTracker[$ID]) { $ConnectionsTracker[$ID] = @{} }
                if (-not $ConnectionsTracker[$ID][$IDParent]) {
                    if ($ADObject.Type -eq 'User') {
                        if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Default') {
                            $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                            if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user -IconColor LightSteelBlue }
                            New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsToEnabled -Dashes
                        }
                    } elseif ($ADObject.Type -eq 'Group') {
                        if ($ADObject.Nesting -eq -1) {
                            $BorderColor = 'Red'
                            $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg'
                        } else {
                            $BorderColor = 'Blue'
                            $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg'
                        }
                        $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers)
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -ArrowsToEnabled -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -ArrowsToEnabled -IconSolid user-friends -IconColor VeryLightGrey }
                        New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsToEnabled
                    } elseif ($ADObject.Type -eq 'Computer') {
                        if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Default') {
                            $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                            if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray }
                            New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsToEnabled -Dashes
                        }
                    } else {
                        if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Default') {
                            $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                            if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon }
                            New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsToEnabled -Dashes
                        }
                    }
                    $ConnectionsTracker[$ID][$IDParent] = $true
                }
            }
        } }
}
function New-HTMLGroupDiagramSummaryHierarchical {
    [cmdletBinding()]
    param([Array] $ADGroup,
        [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both',
        [switch] $HideComputers,
        [switch] $HideUsers,
        [switch] $HideOther,
        [switch] $Online)
    New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsLayout -HierarchicalEnabled $true
        New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200
        if ($ADGroup) {
            foreach ($ADObject in $ADGroup) {
                $ID = "$($ADObject.DomainName)$($ADObject.Name)"
                $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)"
                [int] $Level = $($ADObject.Nesting) + 1
                if ($ADObject.Type -eq 'User') {
                    if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user -IconColor LightSteelBlue }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsToEnabled -Dashes
                    }
                } elseif ($ADObject.Type -eq 'Group') {
                    if ($ADObject.Nesting -eq -1) {
                        $BorderColor = 'Red'
                        $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg'
                    } else {
                        $BorderColor = 'Blue'
                        $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg'
                    }
                    $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers)
                    $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers
                    if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -Level $Level -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user-friends }
                    New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsToEnabled
                } elseif ($ADObject.Type -eq 'Computer') {
                    if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray -Level $Level }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsToEnabled -Dashes
                    }
                } else {
                    if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon -Level $Level }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsToEnabled -Dashes
                    }
                }
            }
        } }
}
function New-HTMLGroupOfDiagramDefault {
    [cmdletBinding()]
    param([Array] $Identity,
        [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both',
        [switch] $HideComputers,
        [switch] $HideUsers,
        [switch] $HideOther,
        [string] $DataTableID,
        [int] $ColumnID,
        [switch] $Online)
    New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion
        if ($Identity) {
            foreach ($ADObject in $Identity) {
                $ID = "$($ADObject.DomainName)$($ADObject.Name)"
                $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)"
                if ($ADObject.Type -eq 'User') {
                    if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Default') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user -IconColor LightSteelBlue }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsFromEnabled -Dashes
                    }
                } elseif ($ADObject.Type -eq 'Group') {
                    if ($ADObject.Nesting -eq -1) {
                        $BorderColor = 'Red'
                        $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg'
                    } else {
                        $BorderColor = 'Blue'
                        $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg'
                    }
                    $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine
                    if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user-friends -IconColor VeryLightGrey }
                    New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsFromEnabled
                } elseif ($ADObject.Type -eq 'Computer') {
                    if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Default') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsFromEnabled -Dashes
                    }
                } else {
                    if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Default') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsFromEnabled -Dashes
                    }
                }
            }
        } }
}
function New-HTMLGroupOfDiagramHierarchical {
    [cmdletBinding()]
    param([Array] $Identity,
        [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both',
        [switch] $HideComputers,
        [switch] $HideUsers,
        [switch] $HideOther,
        [switch] $Online)
    New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsLayout -HierarchicalEnabled $true
        New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200
        if ($Identity) {
            foreach ($ADObject in $Identity) {
                [int] $Level = $($ADObject.Nesting) + 1
                $ID = "$($ADObject.DomainName)$($ADObject.Name)$Level"
                [int] $LevelParent = $($ADObject.Nesting)
                $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)$LevelParent"
                [int] $Level = $($ADObject.Nesting) + 1
                if ($ADObject.Type -eq 'User') {
                    if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user -IconColor LightSteelBlue }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsFromEnabled -Dashes
                    }
                } elseif ($ADObject.Type -eq 'Group') {
                    if ($ADObject.Nesting -eq -1) {
                        $BorderColor = 'Red'
                        $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg'
                    } else {
                        $BorderColor = 'Blue'
                        $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg'
                    }
                    $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine
                    if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -Level $Level -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user-friends }
                    New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsFromEnabled
                } elseif ($ADObject.Type -eq 'Computer') {
                    if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray -Level $Level }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsFromEnabled -Dashes
                    }
                } else {
                    if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon -Level $Level }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsFromEnabled -Dashes
                    }
                }
            }
        } }
}
function New-HTMLGroupOfDiagramSummary {
    [cmdletBinding()]
    param([Array] $ADGroup,
        [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both',
        [switch] $HideComputers,
        [switch] $HideUsers,
        [switch] $HideOther,
        [string] $DataTableID,
        [int] $ColumnID,
        [switch] $Online)
    $ConnectionsTracker = @{}
    New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion
        if ($ADGroup) {
            foreach ($ADObject in $ADGroup) {
                $ID = "$($ADObject.DomainName)$($ADObject.Name)"
                $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)"
                if (-not $ConnectionsTracker[$ID]) { $ConnectionsTracker[$ID] = @{} }
                if (-not $ConnectionsTracker[$ID][$IDParent]) {
                    if ($ADObject.Type -eq 'User') {
                        if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Default') {
                            $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                            if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user -IconColor LightSteelBlue }
                            New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsFromEnabled -Dashes
                        }
                    } elseif ($ADObject.Type -eq 'Group') {
                        if ($ADObject.Nesting -eq -1) {
                            $BorderColor = 'Red'
                            $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg'
                        } else {
                            $BorderColor = 'Blue'
                            $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg'
                        }
                        $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers)
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user-friends -IconColor VeryLightGrey }
                        New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsFromEnabled
                    } elseif ($ADObject.Type -eq 'Computer') {
                        if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Default') {
                            $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                            if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray }
                            New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsFromEnabled -Dashes
                        }
                    } else {
                        if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Default') {
                            $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                            if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon }
                            New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsFromEnabled -Dashes
                        }
                    }
                    $ConnectionsTracker[$ID][$IDParent] = $true
                }
            }
        } }
}
function New-HTMLGroupOfDiagramSummaryHierarchical {
    [cmdletBinding()]
    param([Array] $ADGroup,
        [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both',
        [switch] $HideComputers,
        [switch] $HideUsers,
        [switch] $HideOther,
        [switch] $Online)
    New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsLayout -HierarchicalEnabled $true
        New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200
        if ($ADGroup) {
            foreach ($ADObject in $ADGroup) {
                $ID = "$($ADObject.DomainName)$($ADObject.Name)"
                $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)"
                [int] $Level = $($ADObject.Nesting) + 1
                if ($ADObject.Type -eq 'User') {
                    if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user -IconColor LightSteelBlue }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsFromEnabled -Dashes
                    }
                } elseif ($ADObject.Type -eq 'Group') {
                    if ($ADObject.Nesting -eq -1) {
                        $BorderColor = 'Red'
                        $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg'
                    } else {
                        $BorderColor = 'Blue'
                        $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg'
                    }
                    $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers)
                    $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers
                    if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -Level $Level -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user-friends }
                    New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsFromEnabled
                } elseif ($ADObject.Type -eq 'Computer') {
                    if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray -Level $Level }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsFromEnabled -Dashes
                    }
                } else {
                    if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Hierarchical') {
                        $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName
                        if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon -Level $Level }
                        New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsFromEnabled -Dashes
                    }
                }
            }
        } }
}
function Reset-ADEssentialsStatus {
    [cmdletBinding()]
    param()
    if (-not $Script:DefaultTypes) { $Script:DefaultTypes = foreach ($T in $Script:ADEssentialsConfiguration.Keys) { if ($Script:ADEssentialsConfiguration[$T].Enabled) { $T } } } else {
        foreach ($T in $Script:ADEssentialsConfiguration.Keys) { if ($Script:ADEssentialsConfiguration[$T]) { $Script:ADEssentialsConfiguration[$T]['Enabled'] = $false } }
        foreach ($T in $Script:DefaultTypes) { if ($Script:ADEssentialsConfiguration[$T]) { $Script:ADEssentialsConfiguration[$T]['Enabled'] = $true } }
    }
}
$Script:ADEssentialsConfiguration = [ordered] @{Users = $Script:ShowWinADUser
    Computers                                         = $Script:ShowWinADComputer
    Laps                                              = $Script:ConfigurationLAPS
    LapsAndBitLocker                                  = $Script:ConfigurationLAPSAndBitlocker
    BitLocker                                         = $Script:ConfigurationBitLocker
    ServiceAccounts                                   = $Script:ConfigurationServiceAccounts
}
function Test-ADSubnet {
    [cmdletBinding()]
    param([Array] $Subnets)
    foreach ($Subnet in $Subnets) {
        if ($Subnet.Type -ne 'IPV4') { continue }
        $SmallSubnets = $Subnets | Where-Object { $_.MaskBits -gt $Subnet.MaskBits -and $Subnet.Type -ne 'IPV4' }
        foreach ($SmallSubnet in $SmallSubnets) {
            if (($SmallSubnet.Subnet.Address -band $Subnet.SubnetMask.Address) -eq $Subnet.Subnet.Address) {
                [PSCustomObject]@{Name     = $Subnet.Name
                    SiteName               = $Subnet.SiteName
                    SiteStatus             = $Subnet.SiteStatus
                    SubnetRange            = $Subnet.Subnet
                    OverlappingSubnet      = $SmallSubnet.Name
                    OverlappingSubnetRange = $SmallSubnet.Subnet
                    SiteCollission         = $Subnet.Name -ne $SmallSubnet.Name
                }
            }
        }
    }
}
function Test-DomainTrust {
    [cmdletBinding()]
    param([string] $Domain,
        [string] $TrustedDomain)
    $DomainInformation = Get-WinADDomain -Domain $Domain
    $DomainPDC = $DomainInformation.PdcRoleOwner.Name
    $PropertiesTrustWMI = @('FlatName',
        'SID',
        'TrustAttributes',
        'TrustDirection',
        'TrustedDCName',
        'TrustedDomain',
        'TrustIsOk',
        'TrustStatus',
        'TrustStatusString',
        'TrustType')
    $getCimInstanceSplat = @{ClassName = 'Microsoft_DomainTrustStatus'
        Namespace                      = 'root\MicrosoftActiveDirectory'
        ComputerName                   = $DomainPDC
        ErrorAction                    = 'SilentlyContinue'
        Property                       = $PropertiesTrustWMI
        Verbose                        = $false
    }
    if ($TrustedDomain) { $getCimInstanceSplat['Filter'] = "TrustedDomain = `"$TrustedDomain`"" }
    $TrustStatatuses = Get-CimInstance @getCimInstanceSplat
    if ($TrustStatatuses) {
        foreach ($Status in $TrustStatatuses) {
            [PSCustomObject] @{'TrustSource' = $DomainInformation.Name
                'TrustPartner'               = $Status.TrustedDomain
                'TrustAttributes'            = if ($Status.TrustAttributes) { Get-ADTrustAttributes -Value $Status.TrustAttributes } else { 'Error - needs fixing' }
                'TrustStatus'                = if ($null -ne $Status) { $Status.TrustStatusString } else { 'N/A' }
                'TrustSourceDC'              = if ($null -ne $Status) { $Status.PSComputerName } else { '' }
                'TrustTargetDC'              = if ($null -ne $Status) { $Status.TrustedDCName.Replace('\\', '') } else { '' }
            }
        }
    } else {
        [PSCustomObject] @{'TrustSource' = $DomainInformation.Name
            'TrustPartner'               = $TrustedDomain
            'TrustAttributes'            = 'Error - needs fixing'
            'TrustStatus'                = 'N/A'
            'TrustSourceDC'              = ''
            'TrustTargetDC'              = ''
        }
    }
}
function Test-IPIsInNetwork {
    [cmdletBinding()]
    param([string] $IP,
        [string] $StartBinary,
        [string] $EndBinary)
    $TestIPBinary = Convert-IPToBinary $IP
    [int64] $TestIPInt64 = [System.Convert]::ToInt64($TestIPBinary, 2)
    [int64] $StartInt64 = [System.Convert]::ToInt64($StartBinary, 2)
    [int64] $EndInt64 = [System.Convert]::ToInt64($EndBinary, 2)
    if ($TestIPInt64 -ge $StartInt64 -and $TestIPInt64 -le $EndInt64) { return $True } else { return $False }
}
function Test-LDAPCertificate {
    [CmdletBinding()]
    param([string] $Computer,
        [int] $Port,
        [PSCredential] $Credential)
    $Date = Get-Date
    if ($Credential) { Write-Verbose "Test-LDAPCertificate - Certificate verification $Computer/$Port/Auth Basic" } else { Write-Verbose "Test-LDAPCertificate - Certificate verification $Computer/$Port/Auth Kerberos" }
    $Connection = $null
    $DirectoryIdentifier = [DirectoryServices.Protocols.LdapDirectoryIdentifier]::new($Computer, $Port)
    if ($psboundparameters.ContainsKey("Credential")) {
        $Connection = [DirectoryServices.Protocols.LdapConnection]::new($DirectoryIdentifier, $Credential.GetNetworkCredential())
        $Connection.AuthType = [DirectoryServices.Protocols.AuthType]::Basic
    } else {
        $Connection = [DirectoryServices.Protocols.LdapConnection]::new($DirectoryIdentifier)
        $Connection.AuthType = [DirectoryServices.Protocols.AuthType]::Kerberos
    }
    $Connection.SessionOptions.ProtocolVersion = 3
    $Connection.SessionOptions.SecureSocketLayer = $true
    New-Variable LdapCertificate -Scope Script -Force
    $Connection.SessionOptions.VerifyServerCertificate = { param([DirectoryServices.Protocols.LdapConnection]$Connection,
            [Security.Cryptography.X509Certificates.X509Certificate2]$Certificate)
        $Script:LdapCertificate = $Certificate
        return $true }
    $State = $true
    try {
        $Connection.Bind()
        $ErrorMessage = ''
    } catch {
        $State = $false
        $ErrorMessage = $_.Exception.Message.Trim()
    }
    $KeyExchangeAlgorithm = @{'0' = 'None'
        '43522'                   = 'DiffieHellman'
        '41984'                   = 'RsaKeyX'
        '9216'                    = 'RsaSign'
        '44550'                   = 'ECDH_Ephem'
    }
    if ($Script:LdapCertificate.NotBefore -is [DateTime]) { $X509NotBeforeDays = (New-TimeSpan -Start $Date -End $Script:LdapCertificate.NotBefore).Days } else { $X509NotBeforeDays = $null }
    if ($Script:LdapCertificate.NotAfter -is [DateTime]) { $X509NotAfterDays = (New-TimeSpan -Start $Date -End $Script:LdapCertificate.NotAfter).Days } else { $X509NotAfterDays = $null }
    $Certificate = [ordered]@{State = $State
        X509NotBeforeDays           = $X509NotBeforeDays
        X509NotAfterDays            = $X509NotAfterDays
        X509DnsNameList             = $Script:LdapCertificate.DnsNameList.Unicode
        X509NotBefore               = $Script:LdapCertificate.NotBefore
        X509NotAfter                = $Script:LdapCertificate.NotAfter
        AlgorithmIdentifier         = $Connection.SessionOptions.SslInformation.AlgorithmIdentifier
        CipherStrength              = $Connection.SessionOptions.SslInformation.CipherStrength
        X509FriendlyName            = $Script:LdapCertificate.FriendlyName
        X509SendAsTrustedIssuer     = $Script:LdapCertificate.SendAsTrustedIssuer
        X509SerialNumber            = $Script:LdapCertificate.SerialNumber
        X509Thumbprint              = $Script:LdapCertificate.Thumbprint
        X509SubjectName             = $Script:LdapCertificate.Subject
        X509Issuer                  = $Script:LdapCertificate.Issuer
        X509HasPrivateKey           = $Script:LdapCertificate.HasPrivateKey
        X509Version                 = $Script:LdapCertificate.Version
        X509Archived                = $Script:LdapCertificate.Archived
        Protocol                    = $Connection.SessionOptions.SslInformation.Protocol
        Hash                        = $Connection.SessionOptions.SslInformation.Hash
        HashStrength                = $Connection.SessionOptions.SslInformation.HashStrength
        KeyExchangeAlgorithm        = $KeyExchangeAlgorithm["$($Connection.SessionOptions.SslInformation.KeyExchangeAlgorithm)"]
        ExchangeStrength            = $Connection.SessionOptions.SslInformation.ExchangeStrength
        ErrorMessage                = $ErrorMessage
    }
    $Certificate
}
function Test-LDAPPorts {
    [CmdletBinding()]
    param([string] $ServerName,
        [int] $Port)
    if ($ServerName -and $Port -ne 0) {
        Write-Verbose "Test-LDAPPorts - Processing $ServerName / $Port"
        try {
            $LDAP = "LDAP://" + $ServerName + ':' + $Port
            $Connection = [ADSI]($LDAP)
            $Connection.Close()
            [PSCustomObject] @{Computer = $ServerName
                Port                    = $Port
                Status                  = $true
                ErrorMessage            = ''
            }
        } catch {
            $ErrorMessage = $($_.Exception.Message) -replace [System.Environment]::NewLine
            if ($_.Exception.ToString() -match "The server is not operational") { Write-Warning "Test-LDAPPorts - Can't open $ServerName`:$Port. Error: $ErrorMessage" } elseif ($_.Exception.ToString() -match "The user name or password is incorrect") { Write-Warning "Test-LDAPPorts - Current user ($Env:USERNAME) doesn't seem to have access to to LDAP on port $Server`:$Port. Error: $ErrorMessage" } else { Write-Warning -Message "Test-LDAPPorts - Error: $ErrorMessage" }
            [PSCustomObject] @{Computer = $ServerName
                Port                    = $Port
                Status                  = $false
                ErrorMessage            = $ErrorMessage
            }
        }
    }
}
function Test-LdapServer {
    [cmdletBinding()]
    param([string] $ServerName,
        [string] $Computer,
        [PSCustomObject] $Advanced)
    if ($ServerName -notlike '*.*') {
        $GlobalCatalogSSL = [PSCustomObject] @{Status = $false; ErrorMessage = 'No FQDN' }
        $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP
        $ConnectionLDAPS = [PSCustomObject] @{Status = $false; ErrorMessage = 'No FQDN' }
        $ConnectionLDAP = Test-LDAPPorts -ServerName $ServerName -Port $PortLDAP
        $PortsThatWork = @(if ($GlobalCatalogNonSSL.Status) { $GCPortLDAP }
            if ($GlobalCatalogSSL.Status) { $GCPortLDAPSSL }
            if ($ConnectionLDAP.Status) { $PortLDAP }
            if ($ConnectionLDAPS.Status) { $PortLDAPS }) | Sort-Object
    } else {
        $GlobalCatalogSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAPSSL
        $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP
        $ConnectionLDAPS = Test-LDAPPorts -ServerName $ServerName -Port $PortLDAPS
        $ConnectionLDAP = Test-LDAPPorts -ServerName $ServerName -Port $PortLDAP
        $PortsThatWork = @(if ($GlobalCatalogNonSSL.Status) { $GCPortLDAP }
            if ($GlobalCatalogSSL.Status) { $GCPortLDAPSSL }
            if ($ConnectionLDAP.Status) { $PortLDAP }
            if ($ConnectionLDAPS.Status) { $PortLDAPS }) | Sort-Object
    }
    if ($VerifyCertificate) {
        $Output = [ordered] @{Computer = $ServerName
            Site                       = $Advanced.Site
            IsRO                       = $Advanced.IsReadOnly
            IsGC                       = $Advanced.IsGlobalCatalog
            GlobalCatalogLDAP          = $GlobalCatalogNonSSL.Status
            GlobalCatalogLDAPS         = $GlobalCatalogSSL.Status
            GlobalCatalogLDAPSBind     = $null
            LDAP                       = $ConnectionLDAP.Status
            LDAPS                      = $ConnectionLDAPS.Status
            LDAPSBind                  = $null
            AvailablePorts             = $PortsThatWork -join ','
            X509NotBeforeDays          = $null
            X509NotAfterDays           = $null
            X509DnsNameList            = $null
            OperatingSystem            = $Advanced.OperatingSystem
            IPV4Address                = $Advanced.IPV4Address
            IPV6Address                = $Advanced.IPV6Address
            X509NotBefore              = $null
            X509NotAfter               = $null
            AlgorithmIdentifier        = $null
            CipherStrength             = $null
            X509FriendlyName           = $null
            X509SendAsTrustedIssuer    = $null
            X509SerialNumber           = $null
            X509Thumbprint             = $null
            X509SubjectName            = $null
            X509Issuer                 = $null
            X509HasPrivateKey          = $null
            X509Version                = $null
            X509Archived               = $null
            Protocol                   = $null
            Hash                       = $null
            HashStrength               = $null
            KeyExchangeAlgorithm       = $null
            ExchangeStrength           = $null
            ErrorMessage               = $null
        }
    } else {
        $Output = [ordered] @{Computer = $ServerName
            Site                       = $Advanced.Site
            IsRO                       = $Advanced.IsReadOnly
            IsGC                       = $Advanced.IsGlobalCatalog
            GlobalCatalogLDAP          = $GlobalCatalogNonSSL.Status
            GlobalCatalogLDAPS         = $GlobalCatalogSSL.Status
            GlobalCatalogLDAPSBind     = $null
            LDAP                       = $ConnectionLDAP.Status
            LDAPS                      = $ConnectionLDAPS.Status
            LDAPSBind                  = $null
            AvailablePorts             = $PortsThatWork -join ','
            OperatingSystem            = $Advanced.OperatingSystem
            IPV4Address                = $Advanced.IPV4Address
            IPV6Address                = $Advanced.IPV6Address
        }
    }
    if ($VerifyCertificate) {
        if ($psboundparameters.ContainsKey("Credential")) {
            $Certificate = Test-LDAPCertificate -Computer $ServerName -Port $PortLDAPS -Credential $Credential
            $CertificateGC = Test-LDAPCertificate -Computer $ServerName -Port $GCPortLDAPSSL -Credential $Credential
        } else {
            $Certificate = Test-LDAPCertificate -Computer $ServerName -Port $PortLDAPS
            $CertificateGC = Test-LDAPCertificate -Computer $ServerName -Port $GCPortLDAPSSL
        }
        $Output['LDAPSBind'] = $Certificate.State
        $Output['GlobalCatalogLDAPSBind'] = $CertificateGC.State
        $Output['X509NotBeforeDays'] = $Certificate['X509NotBeforeDays']
        $Output['X509NotAfterDays'] = $Certificate['X509NotAfterDays']
        $Output['X509DnsNameList'] = $Certificate['X509DnsNameList']
        $Output['X509NotBefore'] = $Certificate['X509NotBefore']
        $Output['X509NotAfter'] = $Certificate['X509NotAfter']
        $Output['AlgorithmIdentifier'] = $Certificate['AlgorithmIdentifier']
        $Output['CipherStrength'] = $Certificate['CipherStrength']
        $Output['X509FriendlyName'] = $Certificate['X509FriendlyName']
        $Output['X509SendAsTrustedIssuer'] = $Certificate['X509SendAsTrustedIssuer']
        $Output['X509SerialNumber'] = $Certificate['X509SerialNumber']
        $Output['X509Thumbprint'] = $Certificate['X509Thumbprint']
        $Output['X509SubjectName'] = $Certificate['X509SubjectName']
        $Output['X509Issuer'] = $Certificate['X509Issuer']
        $Output['X509HasPrivateKey'] = $Certificate['X509HasPrivateKey']
        $Output['X509Version'] = $Certificate['X509Version']
        $Output['X509Archived'] = $Certificate['X509Archived']
        $Output['Protocol'] = $Certificate['Protocol']
        $Output['Hash'] = $Certificate['Hash']
        $Output['HashStrength'] = $Certificate['HashStrength']
        $Output['KeyExchangeAlgorithm'] = $Certificate['KeyExchangeAlgorithm']
        $Output['ExchangeStrength'] = $Certificate['ExchangeStrength']
        $Output['ErrorMessage'] = $Certificate['ErrorMessage']
    } else {
        $Output.Remove('LDAPSBind')
        $Output.Remove('GlobalCatalogLDAPSBind')
    }
    if (-not $Advanced) {
        $Output.Remove('IPV4Address')
        $Output.Remove('OperatingSystem')
        $Output.Remove('IPV6Address')
        $Output.Remove('Site')
        $Output.Remove('IsRO')
        $Output.Remove('IsGC')
    }
    [PSCustomObject] $Output
}
function Add-ADACL {
    [cmdletBinding(SupportsShouldProcess)]
    param([Parameter(Mandatory)][Array] $ACL,
        [Parameter(Mandatory)][string] $Principal,
        [Parameter(Mandatory)][System.DirectoryServices.ActiveDirectoryRights] $AccessRule,
        [Parameter(Mandatory)][System.Security.AccessControl.AccessControlType] $AccessControlType)
    if ($Principal -is [string]) {
        if ($Principal -like '*/*') {
            $SplittedName = $Principal -split '/'
            [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($SplittedName[0], $SplittedName[1])
        } else { [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal) }
    } else { return }
    foreach ($SubACL in $ACL) {
        $OutputRequiresCommit = foreach ($Rule in $AccessRule) {
            $AccessRuleToAdd = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $Rule, $AccessControlType)
            try {
                Write-Verbose "Add-ADACL - Adding access for $($AccessRuleToAdd.IdentityReference) / $($AccessRuleToAdd.ActiveDirectoryRights)"
                $SubACL.ACL.AddAccessRule($AccessRuleToAdd)
                $true
            } catch {
                Write-Warning "Add-ADACL - Error adding permissions for $($AccessRuleToAdd.IdentityReference) / $($AccessRuleToAdd.ActiveDirectoryRights) due to error: $($_.Exception.Message)"
                $false
            }
        }
        if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) {
            Write-Verbose "Add-ADACL - Saving permissions for $($SubACL.DistinguishedName)"
            Set-Acl -Path $SubACL.Path -AclObject $SubACL.ACL -ErrorAction Stop
        } elseif ($OutputRequiresCommit -contains $false) { Write-Warning "Add-ADACL - Skipping saving permissions for $($SubACL.DistinguishedName) due to errors." }
    }
}
function Get-ADACL {
    [cmdletbinding()]
    param([Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)][Array] $ADObject,
        [string] $ForestName,
        [switch] $Extended,
        [alias('ResolveTypes')][switch] $Resolve,
        [switch] $Inherited,
        [switch] $NotInherited,
        [switch] $Bundle,
        [System.Security.AccessControl.AccessControlType] $AccessControlType,
        [string[]] $IncludeObjectTypeName,
        [string[]] $IncludeInheritedObjectTypeName,
        [string[]] $ExcludeObjectTypeName,
        [string[]] $ExcludeInheritedObjectTypeName,
        [System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRights,
        [System.DirectoryServices.ActiveDirectoryRights[]] $ExcludeActiveDirectoryRights,
        [System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $IncludeActiveDirectorySecurityInheritance,
        [System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $ExcludeActiveDirectorySecurityInheritance,
        [switch] $ADRightsAsArray)
    Begin {
        if (-not $Script:ForestGUIDs) {
            Write-Verbose "Get-ADACL - Gathering Forest GUIDS"
            $Script:ForestGUIDs = Get-WinADForestGUIDs
        }
    }
    Process {
        foreach ($Object in $ADObject) {
            if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) {
                [string] $DistinguishedName = $Object.DistinguishedName
                [string] $CanonicalName = $Object.CanonicalName.TrimEnd('/')
                [string] $ObjectClass = $Object.ObjectClass
            } elseif ($Object -is [string]) {
                [string] $DistinguishedName = $Object
                [string] $CanonicalName = ''
                [string] $ObjectClass = ''
            } else {
                Write-Warning "Get-ADACL - Object not recognized. Skipping..."
                continue
            }
            $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDC) -replace '=' -replace ','
            if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                Write-Verbose "Get-ADACL - Enabling PSDrives for $DistinguishedName to $DNConverted"
                New-ADForestDrives -ForestName $ForestName
                if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                    Write-Warning "Get-ADACL - Drive $DNConverted not mapped. Terminating..."
                    return
                }
            }
            Write-Verbose "Get-ADACL - Getting ACL from $DistinguishedName"
            try {
                $PathACL = "$DNConverted`:\$($DistinguishedName)"
                $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop
            } catch { Write-Warning "Get-ADACL - Path $PathACL - Error: $($_.Exception.Message)" }
            $AccessObjects = foreach ($ACL in $ACLs.Access) {
                [Array] $ADRights = $ACL.ActiveDirectoryRights -split ', '
                if ($AccessControlType) { if ($ACL.AccessControlType -ne $AccessControlType) { continue } }
                if ($Inherited) { if ($ACL.IsInherited -eq $false) { continue } }
                if ($NotInherited) { if ($ACL.IsInherited -eq $true) { continue } }
                if ($IncludeActiveDirectoryRights) {
                    $FoundInclude = $false
                    foreach ($Right in $ADRights) {
                        if ($IncludeActiveDirectoryRights -contains $Right) {
                            $FoundInclude = $true
                            break
                        }
                    }
                    if (-not $FoundInclude) { continue }
                }
                if ($ExcludeActiveDirectoryRights) {
                    foreach ($Right in $ADRights) {
                        $FoundExclusion = $false
                        if ($ExcludeActiveDirectoryRights -contains $Right) {
                            $FoundExclusion = $true
                            break
                        }
                        if ($FoundExclusion) { continue }
                    }
                }
                if ($IncludeActiveDirectorySecurityInheritance) { if ($IncludeActiveDirectorySecurityInheritance -notcontains $ACL.InheritanceType) { continue } }
                if ($ExcludeActiveDirectorySecurityInheritance) { if ($ExcludeActiveDirectorySecurityInheritance -contains $ACL.InheritanceType) { continue } }
                $IdentityReference = $ACL.IdentityReference.Value
                $ReturnObject = [ordered] @{}
                $ReturnObject['DistinguishedName' ] = $DistinguishedName
                if ($CanonicalName) { $ReturnObject['CanonicalName'] = $CanonicalName }
                if ($ObjectClass) { $ReturnObject['ObjectClass'] = $ObjectClass }
                $ReturnObject['AccessControlType'] = $ACL.AccessControlType
                $ReturnObject['Principal'] = $IdentityReference
                if ($Resolve) {
                    $IdentityResolve = Get-WinADObject -Identity $IdentityReference -AddType -Verbose:$false
                    if (-not $IdentityResolve) {
                        $ConvertIdentity = Convert-Identity -Identity $IdentityReference -Verbose:$false
                        $ReturnObject['PrincipalType'] = $ConvertIdentity.Type
                        $ReturnObject['PrincipalObjectType'] = 'foreignSecurityPrincipal'
                        $ReturnObject['PrincipalObjectDomain'] = $ConvertIdentity.DomainName
                        $ReturnObject['PrincipalObjectSid'] = $ConvertIdentity.SID
                    } else {
                        if ($ReturnObject['Principal']) { $ReturnObject['Principal'] = $IdentityResolve.Name }
                        $ReturnObject['PrincipalType'] = $IdentityResolve.Type
                        $ReturnObject['PrincipalObjectType'] = $IdentityResolve.ObjectClass
                        $ReturnObject['PrincipalObjectDomain' ] = $IdentityResolve.DomainName
                        $ReturnObject['PrincipalObjectSid'] = $IdentityResolve.ObjectSID
                    }
                    if (-not $ReturnObject['PrincipalObjectDomain']) { $ReturnObject['PrincipalObjectDomain'] = ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDomainCN }
                }
                $ReturnObject['ObjectTypeName'] = $Script:ForestGUIDs["$($ACL.objectType)"]
                $ReturnObject['InheritedObjectTypeName'] = $Script:ForestGUIDs["$($ACL.inheritedObjectType)"]
                if ($IncludeObjectTypeName) { if ($IncludeObjectTypeName -notcontains $ReturnObject['ObjectTypeName']) { continue } }
                if ($IncludeInheritedObjectTypeName) { if ($IncludeInheritedObjectTypeName -notcontains $ReturnObject['InheritedObjectTypeName']) { continue } }
                if ($ExcludeObjectTypeName) { if ($ExcludeObjectTypeName -contains $ReturnObject['ObjectTypeName']) { continue } }
                if ($ExcludeInheritedObjectTypeName) { if ($ExcludeInheritedObjectTypeName -contains $ReturnObject['InheritedObjectTypeName']) { continue } }
                if ($ADRightsAsArray) { $ReturnObject['ActiveDirectoryRights'] = $ADRights } else { $ReturnObject['ActiveDirectoryRights'] = $ACL.ActiveDirectoryRights }
                $ReturnObject['InheritanceType'] = $ACL.InheritanceType
                $ReturnObject['IsInherited'] = $ACL.IsInherited
                if ($Extended) {
                    $ReturnObject['ObjectType'] = $ACL.ObjectType
                    $ReturnObject['InheritedObjectType'] = $ACL.InheritedObjectType
                    $ReturnObject['ObjectFlags'] = $ACL.ObjectFlags
                    $ReturnObject['InheritanceFlags'] = $ACL.InheritanceFlags
                    $ReturnObject['PropagationFlags'] = $ACL.PropagationFlags
                }
                if ($Bundle) { $ReturnObject['Bundle'] = $ACL }
                [PSCustomObject] $ReturnObject
            }
            if ($Bundle) {
                [PSCustomObject] @{DistinguishedName = $DistinguishedName
                    CanonicalName                    = $Object.CanonicalName
                    ACL                              = $ACLs
                    ACLAccessRules                   = $AccessObjects
                    Path                             = $PathACL
                }
            } else { $AccessObjects }
        }
    }
    End {}
}
function Get-ADACLOwner {
    [cmdletBinding()]
    param([Array] $ADObject,
        [switch] $Resolve,
        [switch] $AddACL,
        [System.Collections.IDictionary] $ADAdministrativeGroups,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    Begin {
        if (-not $ADAdministrativeGroups -and $Resolve) {
            $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
            $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation
        }
    }
    Process {
        foreach ($Object in $ADObject) {
            if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) {
                [string] $DistinguishedName = $Object.DistinguishedName
                [string] $CanonicalName = $Object.CanonicalName
                [string] $ObjectClass = $Object.ObjectClass
            } elseif ($Object -is [string]) {
                [string] $DistinguishedName = $Object
                [string] $CanonicalName = ''
                [string] $ObjectClass = ''
            } else {
                Write-Warning "Get-ADACLOwner - Object not recognized. Skipping..."
                continue
            }
            $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDC) -replace '=' -replace ','
            if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                Write-Verbose "Get-ADACLOwner - Enabling PSDrives for $DistinguishedName to $DNConverted"
                New-ADForestDrives -ForestName $ForestName
                if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                    Write-Warning "Set-ADACLOwner - Drive $DNConverted not mapped. Terminating..."
                    return
                }
            }
            $PathACL = "$DNConverted`:\$($DistinguishedName)"
            try {
                $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop
                $Hash = [ordered] @{DistinguishedName = $DistinguishedName
                    CanonicalName                     = $CanonicalName
                    ObjectClass                       = $ObjectClass
                    Owner                             = $ACLs.Owner
                }
                $ErrorMessage = ''
            } catch {
                $ACLs = $null
                $Hash = [ordered] @{DistinguishedName = $DistinguishedName
                    CanonicalName                     = $CanonicalName
                    ObjectClass                       = $ObjectClass
                    Owner                             = $null
                }
                $ErrorMessage = $_.Exception.Message
            }
            if ($AddACL) { $Hash['ACLs'] = $ACLs }
            if ($Resolve) {
                if ($null -eq $Hash.Owner) { $Identity = $null } else { $Identity = Convert-Identity -Identity $Hash.Owner -Verbose:$false }
                if ($Identity) {
                    $Hash['OwnerName'] = $Identity.Name
                    $Hash['OwnerSid'] = $Identity.SID
                    $Hash['OwnerType'] = $Identity.Type
                } else {
                    $Hash['OwnerName'] = ''
                    $Hash['OwnerSid'] = ''
                    $Hash['OwnerType'] = ''
                }
            }
            $Hash['Error'] = $ErrorMessage
            [PSCustomObject] $Hash
        }
    }
    End {}
}
function Get-DNSServerIP {
    [alias('Get-WinDNSServerIP')]
    param([string[]] $ComputerName,
        [string[]] $ApprovedList,
        [pscredential] $Credential)
    foreach ($Computer in $ComputerName) {
        $Adapters = Get-CimData -Class Win32_NetworkAdapterConfiguration -ComputerName $Computer -ErrorAction Stop | Where-Object { $_.DHCPEnabled -ne 'True' -and $null -ne $_.DNSServerSearchOrder }
        if ($Adapters) {
            foreach ($Adapter in $Adapters) {
                $AllApproved = $true
                foreach ($DNS in $Adapter.DNSServerSearchOrder) { if ($DNS -notin $ApprovedList) { $AllApproved = $true } }
                $AtLeastTwo = $Adapter.DNSServerSearchOrder.Count -ge 2
                $Output = [ordered] @{DNSHostName = $Adapter.DNSHostName
                    Status                        = $AllApproved -and $AtLeastTwo
                    Approved                      = $AllApproved
                    AtLeastTwo                    = $AtLeastTwo
                    Connected                     = $true
                    IPAddress                     = $Adapter.IPAddress -join ', '
                    DNSServerSearchOrder          = $Adapter.DNSServerSearchOrder -join ', '
                    DefaultIPGateway              = $Adapter.DefaultIPGateway -join ', '
                    IPSubnet                      = $Adapter.IPSubnet -join ', '
                    Description                   = $Adapter.Description
                }
                if (-not $ApprovedList) {
                    $Output.Remove('Approved')
                    $Output.Remove('Status')
                }
                [PSCustomObject] $Output
            }
        } else {
            $Output = [ordered] @{DNSHostName = $Computer
                Status                        = $false
                Approved                      = $false
                AtLeastTwo                    = $false
                Connected                     = $false
                IPAddress                     = $null
                DNSServerSearchOrder          = $null
                DefaultIPGateway              = $null
                IPSubnet                      = $null
                Description                   = $ErrorMessage
            }
            if (-not $ApprovedList) {
                $Output.Remove('Approved')
                $Output.Remove('Status')
            }
            [PSCustomObject] $Output
        }
    }
}
function Get-WinADACLConfiguration {
    <#
    .SYNOPSIS
    Gets permissions or owners from configuration partition
 
    .DESCRIPTION
    Gets permissions or owners from configuration partition for one or multiple types
 
    .PARAMETER ObjectType
    Gets permissions or owners from one or multiple types (and only that type). Possible choices are sites, subnets, interSiteTransport, siteLink, wellKnownSecurityPrincipals
 
    .PARAMETER ContainerType
    Gets permissions or owners from one or multiple types (including containers and anything below it). Possible choices are sites, subnets, interSiteTransport, siteLink, wellKnownSecurityPrincipals, services
 
    .PARAMETER Owner
    Queries for Owners, instead of permissions
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .EXAMPLE
    Get-WinADACLConfiguration -ObjectType 'interSiteTransport', 'siteLink', 'wellKnownSecurityPrincipals' | Format-Table
 
    .EXAMPLE
    Get-WinADACLConfiguration -ContainerType 'sites' -Owner | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'ObjectType')]
    param([parameter(ParameterSetName = 'ObjectType', Mandatory)][ValidateSet('site', 'subnet', 'interSiteTransport', 'siteLink', 'wellKnownSecurityPrincipal')][string[]] $ObjectType,
        [parameter(ParameterSetName = 'FolderType', Mandatory)][ValidateSet('site', 'subnet', 'interSiteTransport', 'siteLink', 'wellKnownSecurityPrincipal', 'service')][string[]] $ContainerType,
        [switch] $Owner,
        [string] $Forest,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0]
    $ForestDN = ConvertTo-DistinguishedName -ToDomain -CanonicalName $ForestInformation.Forest.Name
    if ($ObjectType) {
        if ($ObjectType -contains 'site') {
            $getADObjectSplat = @{Server = $QueryServer
                LDAPFilter               = '(objectClass=site)'
                SearchBase               = "CN=Sites,CN=Configuration,$($($ForestDN))"
                SearchScope              = 'OneLevel'
                Properties               = 'Name', 'CanonicalName', 'DistinguishedName', 'WhenCreated', 'WhenChanged', 'ObjectClass', 'ProtectedFromAccidentalDeletion', 'siteobjectbl', 'gplink', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Site' -Owner:$Owner
        }
        if ($ObjectType -contains 'subnet') {
            $getADObjectSplat = @{Server = $QueryServer
                LDAPFilter               = '(objectClass=subnet)'
                SearchBase               = "CN=Subnets,CN=Sites,CN=Configuration,$($($ForestDN))"
                SearchScope              = 'OneLevel'
                Properties               = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Subnet' -Owner:$Owner
        }
        if ($ObjectType -contains 'interSiteTransport') {
            $getADObjectSplat = @{Server = $QueryServer
                LDAPFilter               = '(objectClass=interSiteTransport)'
                SearchBase               = "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$($($ForestDN))"
                SearchScope              = 'OneLevel'
                Properties               = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'InterSiteTransport' -Owner:$Owner
        }
        if ($ObjectType -contains 'siteLink') {
            $getADObjectSplat = @{Server = $QueryServer
                LDAPFilter               = '(objectClass=siteLink)'
                SearchBase               = "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$($($ForestDN))"
                SearchScope              = 'OneLevel'
                Properties               = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Site' -Owner:$Owner
        }
        if ($ObjectType -contains 'wellKnownSecurityPrincipal') {
            $getADObjectSplat = @{Server = $QueryServer
                LDAPFilter               = '(objectClass=foreignSecurityPrincipal)'
                SearchBase               = "CN=WellKnown Security Principals,CN=Configuration,$($($ForestDN))"
                SearchScope              = 'OneLevel'
                Properties               = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'WellKnownSecurityPrincipals' -Owner:$Owner
        }
    } else {
        if ($ContainerType -contains 'site') {
            $getADObjectSplat = @{Server = $QueryServer
                Filter                   = "*"
                SearchBase               = "CN=Sites,CN=Configuration,$($($ForestDN))"
                Properties               = 'Name', 'CanonicalName', 'DistinguishedName', 'WhenCreated', 'WhenChanged', 'ObjectClass', 'ProtectedFromAccidentalDeletion', 'siteobjectbl', 'gplink', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Site' -FilterOut -Owner:$Owner
        }
        if ($ContainerType -contains 'subnet') {
            $getADObjectSplat = @{Server = $QueryServer
                Filter                   = "*"
                SearchBase               = "CN=Subnets,CN=Sites,CN=Configuration,$($($ForestDN))"
                Properties               = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Subnet' -Owner:$Owner
        }
        if ($ContainerType -contains 'interSiteTransport') {
            $getADObjectSplat = @{Server = $QueryServer
                Filter                   = '*'
                SearchBase               = "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$($($ForestDN))"
                Properties               = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'InterSiteTransport' -Owner:$Owner
        }
        if ($ContainerType -contains 'siteLink') {
            $getADObjectSplat = @{Server = $QueryServer
                Filter                   = '*'
                SearchBase               = "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$($($ForestDN))"
                Properties               = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Site' -Owner:$Owner
        }
        if ($ContainerType -contains 'service') {
            $getADObjectSplat = @{Server = $QueryServer
                Filter                   = '*'
                SearchBase               = "CN=Services,CN=Configuration,$($($ForestDN))"
                Properties               = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'service' -Owner:$Owner
        }
        if ($ContainerType -contains 'wellKnownSecurityPrincipal') {
            $getADObjectSplat = @{Server = $QueryServer
                Filter                   = '*'
                SearchBase               = "CN=WellKnown Security Principals,CN=Configuration,$($($ForestDN))"
                Properties               = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description'
            }
            Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'WellKnownSecurityPrincipals' -Owner:$Owner
        }
    }
}
function Get-WinADACLForest {
    <#
    .SYNOPSIS
    Gets permissions or owners from forest
 
    .DESCRIPTION
    Gets permissions or owners from forest
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER Owner
    Queries for Owners, instead of permissions
 
    .PARAMETER Separate
    Returns OrderedDictionary with each top level container being in separate key
 
    .EXAMPLE
    # With split per sheet
    $FilePath = "$Env:USERPROFILE\Desktop\PermissionsOutputPerSheet.xlsx"
    $Permissions = Get-WinADACLForest -Verbose -SplitWorkSheets
    foreach ($Perm in $Permissions.Keys) {
        $Permissions[$Perm] | ConvertTo-Excel -FilePath $FilePath -ExcelWorkSheetName $Perm -AutoFilter -AutoFit -FreezeTopRowFirstColumn
    }
    $Permissions | Format-Table *
 
    .EXAMPLE
    # With owners in one sheet
    $FilePath = "$Env:USERPROFILE\Desktop\PermissionsOutput.xlsx"
    $Permissions = Get-WinADACLForest -Verbose
    $Permissions | ConvertTo-Excel -FilePath $FilePath -ExcelWorkSheetName 'Permissions' -AutoFilter -AutoFit -FreezeTopRowFirstColumn
    $Permissions | Format-Table *
 
    .EXAMPLE
    # With split per sheet
    $FilePath = "$Env:USERPROFILE\Desktop\OwnersOutput.xlsx"
    $Owners = Get-WinADACLForest -Verbose -SplitWorkSheets -Owner
    foreach ($Owner in $Owners.Keys) {
        $Owners[$Owner] | ConvertTo-Excel -FilePath $FilePath -ExcelWorkSheetName $Owner -AutoFilter -AutoFit -FreezeTopRowFirstColumn
    }
    $Owners | Format-Table *
 
    .EXAMPLE
    # With owners in one sheet
    $FilePath = "$Env:USERPROFILE\Desktop\OwnersOutput.xlsx"
    $Owners = Get-WinADACLForest -Verbose -Owner
    $Owners | ConvertTo-Excel -FilePath $FilePath -ExcelWorkSheetName 'AllOwners' -AutoFilter -AutoFit -FreezeTopRowFirstColumn
    $Owners | Format-Table *
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param([string] $Forest,
        [alias('Domain')][string[]] $IncludeDomains,
        [string[]] $ExcludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [switch] $Owner,
        [switch] $Separate)
    $ForestTime = Start-TimeLog
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended
    $Output = [ordered]@{}
    foreach ($Domain in $ForestInformation.Domains) {
        Write-Verbose "Get-WinADACLForest - [Start][Domain $Domain]"
        $DomainTime = Start-TimeLog
        $Output[$Domain] = [ordered] @{}
        $Server = $ForestInformation.QueryServers[$Domain].HostName[0]
        $DomainStructure = @(Get-ADObject -Filter * -Properties canonicalName -SearchScope Base -Server $Server
            Get-ADObject -Filter * -Properties canonicalName -SearchScope OneLevel -Server $Server)
        $DomainStructure = $DomainStructure | Sort-Object -Property canonicalName
        foreach ($Structure in $DomainStructure) {
            $Time = Start-TimeLog
            $ObjectName = "[$Domain][$($Structure.CanonicalName)][$($Structure.ObjectClass)][$($Structure.DistinguishedName)]"
            $ObjectOutputName = "$($Structure.Name)_$($Structure.ObjectClass)".Replace(' ', '').ToLower()
            Write-Verbose "Get-WinADACLForest - [Start]$ObjectName"
            if ($Structure.ObjectClass -eq 'organizationalUnit') { $Containers = Get-ADObject -SearchBase $Structure.DistinguishedName -Filter * -Properties canonicalName -Server $Server -SearchScope Subtree | ForEach-Object { foreach ($I in $Ignore) { if ($_.DistinguishedName -notlike $I) { $_ } } } | Sort-Object canonicalName } elseif ($Structure.ObjectClass -eq 'domainDNS') { $Containers = $Structure } elseif ($Structure.ObjectClass -eq 'container') {
                $Ignore = @( -join ('*CN=Policies,CN=System,', $ForestInformation['DomainsExtended'][$DOmain].DistinguishedName))
                $Containers = Get-ADObject -SearchBase $Structure.DistinguishedName -Filter * -Properties canonicalName -Server $Server -SearchScope Subtree | ForEach-Object { foreach ($I in $Ignore) { if ($_.DistinguishedName -notlike $I) { $_ } } } | Sort-Object canonicalName
            } else {
                Write-Verbose "Get-WinADACLForest - [Skip ]$ObjectName[ObjectClass not requested]"
                continue
            }
            if ($Owner) { $MYACL = Get-ADACLOwner -ADObject $Containers -Resolve } else { $MYACL = Get-ADACL -ADObject $Containers -NotInherited -ResolveTypes }
            if ($Separate) { $Output[$Domain][$ObjectOutputName] = $MYACL } else { $MYACL }
            $EndTime = Stop-TimeLog -Time $Time -Option OneLiner
            Write-Verbose "Get-WinADACLForest - [End ]$ObjectName[$EndTime]"
        }
        $DomainEndTime = Stop-TimeLog -Time $DomainTime -Option OneLiner
        Write-Verbose "Get-WinADACLForest - [End ][Domain $Domain][$DomainEndTime]"
    }
    $ForestEndTime = Stop-TimeLog -Time $ForestTime -Option OneLiner
    Write-Verbose "Get-WinADACLForest - [End ][Forest][$ForestEndTime]"
    if ($Separate) { $Output }
}
function Get-WinADBitlockerLapsSummary {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param([Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'LapsOnly')]
        [Parameter(ParameterSetName = 'BitlockerOnly')]
        [alias('ForestName')][string] $Forest,
        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'LapsOnly')]
        [Parameter(ParameterSetName = 'BitlockerOnly')]
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'LapsOnly')]
        [Parameter(ParameterSetName = 'BitlockerOnly')]
        [string[]] $ExcludeDomains,
        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'LapsOnly')]
        [Parameter(ParameterSetName = 'BitlockerOnly')]
        [string] $Filter = '*',
        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'LapsOnly')]
        [Parameter(ParameterSetName = 'BitlockerOnly')]
        [string] $SearchBase,
        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'LapsOnly')]
        [Parameter(ParameterSetName = 'BitlockerOnly')]
        [ValidateSet('Base', 'OneLevel', 'SubTree', 'None')] [string] $SearchScope = 'None',
        [Parameter(ParameterSetName = 'LapsOnly')][switch] $LapsOnly,
        [Parameter(ParameterSetName = 'BitlockerOnly')][switch] $BitlockerOnly,
        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'LapsOnly')]
        [Parameter(ParameterSetName = 'BitlockerOnly')]
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $Today = Get-Date
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $ComputerProperties = Get-WinADForestSchemaProperties -Schema 'Computers' -Forest $Forest -ExtendedForestInformation $ForestInformation
    if ($ComputerProperties.Name -contains 'ms-Mcs-AdmPwd') {
        $LapsAvailable = $true
        $Properties = @('Name'
            'OperatingSystem'
            'OperatingSystemVersion'
            'DistinguishedName'
            'LastLogonDate'
            'PasswordLastSet'
            'ms-Mcs-AdmPwd'
            'ms-Mcs-AdmPwdExpirationTime'
            'PrimaryGroupID')
    } else {
        $LapsAvailable = $false
        $Properties = @('Name'
            'OperatingSystem'
            'OperatingSystemVersion'
            'DistinguishedName'
            'LastLogonDate'
            'PasswordLastSet'
            'PrimaryGroupID')
    }
    $CurrentDate = Get-Date
    $FormattedComputers = foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $Parameters = @{}
        if ($SearchScope -ne 'None') { $Parameters.SearchScope = $SearchScope }
        if ($SearchBase) {
            $DomainInformation = Get-ADDomain -Server $QueryServer
            $DNExtract = ConvertFrom-DistinguishedName -DistinguishedName $SearchBase -ToDC
            if ($DNExtract -eq $DomainInformation.DistinguishedName) { $Parameters.SearchBase = $SearchBase } else { continue }
        }
        try { $Computers = Get-ADComputer -Filter $Filter -Properties $Properties -Server $QueryServer @Parameters -ErrorAction Stop } catch { Write-Warning "Get-WinADBitlockerLapsSummary - Error getting computers $($_.Exception.Message)" }
        foreach ($_ in $Computers) {
            if ($LapsOnly -or -not $BitlockerOnly) {
                if ($LapsAvailable) {
                    if ($_.'ms-Mcs-AdmPwdExpirationTime') {
                        $Laps = $true
                        $LapsExpirationDays = Convert-TimeToDays -StartTime ($CurrentDate) -EndTime (Convert-ToDateTime -Timestring ($_.'ms-Mcs-AdmPwdExpirationTime'))
                        $LapsExpirationTime = Convert-ToDateTime -Timestring ($_.'ms-Mcs-AdmPwdExpirationTime')
                    } else {
                        $Laps = $false
                        $LapsExpirationDays = $null
                        $LapsExpirationTime = $null
                    }
                } else { $Laps = 'N/A' }
            }
            if (-not $LapsOnly -or $BitlockerOnly) {
                [Array] $Bitlockers = Get-ADObject -Server $QueryServer -Filter 'objectClass -eq "msFVE-RecoveryInformation"' -SearchBase $_.DistinguishedName -Properties 'WhenCreated', 'msFVE-RecoveryPassword' | Sort-Object -Descending
                if ($Bitlockers) {
                    $Encrypted = $true
                    $EncryptedTime = $Bitlockers[0].WhenCreated
                } else {
                    $Encrypted = $false
                    $EncryptedTime = $null
                }
            }
            if ($null -ne $_.LastLogonDate) { [int] $LastLogonDays = "$(-$($_.LastLogonDate - $Today).Days)" } else { $LastLogonDays = $null }
            if ($null -ne $_.PasswordLastSet) { [int] $PasswordLastChangedDays = "$(-$($_.PasswordLastSet - $Today).Days)" } else { $PasswordLastChangedDays = $null }
            if ($LapsOnly) {
                [PSCustomObject] @{Name     = $_.Name
                    Enabled                 = $_.Enabled
                    Domain                  = $Domain
                    DNSHostName             = $_.DNSHostName
                    IsDC                    = if ($_.PrimaryGroupID -in 516, 521) { $true } else { $false }
                    Laps                    = $Laps
                    LapsExpirationDays      = $LapsExpirationDays
                    LapsExpirationTime      = $LapsExpirationTime
                    System                  = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
                    LastLogonDate           = $_.LastLogonDate
                    LastLogonDays           = $LastLogonDays
                    PasswordLastSet         = $_.PasswordLastSet
                    PasswordLastChangedDays = $PasswordLastChangedDays
                    OrganizationalUnit      = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit
                    DistinguishedName       = $_.DistinguishedName
                }
            } elseif ($BitlockerOnly) {
                [PSCustomObject] @{Name     = $_.Name
                    Enabled                 = $_.Enabled
                    Domain                  = $Domain
                    DNSHostName             = $_.DNSHostName
                    IsDC                    = if ($Computer.PrimaryGroupID -in 516, 521) { $true } else { $false }
                    Encrypted               = $Encrypted
                    EncryptedTime           = $EncryptedTime
                    System                  = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
                    LastLogonDate           = $_.LastLogonDate
                    LastLogonDays           = $LastLogonDays
                    PasswordLastSet         = $_.PasswordLastSet
                    PasswordLastChangedDays = $PasswordLastChangedDays
                    OrganizationalUnit      = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit
                    DistinguishedName       = $_.DistinguishedName
                }
            } else {
                [PSCustomObject] @{Name     = $_.Name
                    Enabled                 = $_.Enabled
                    Domain                  = $Domain
                    DNSHostName             = $_.DNSHostName
                    IsDC                    = if ($Computer.PrimaryGroupID -in 516, 521) { $true } else { $false }
                    Encrypted               = $Encrypted
                    EncryptedTime           = $EncryptedTime
                    Laps                    = $Laps
                    LapsExpirationDays      = $LapsExpirationDays
                    LapsExpirationTime      = $LapsExpirationTime
                    System                  = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
                    LastLogonDate           = $_.LastLogonDate
                    LastLogonDays           = $LastLogonDays
                    PasswordLastSet         = $_.PasswordLastSet
                    PasswordLastChangedDays = $PasswordLastChangedDays
                    OrganizationalUnit      = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit
                    DistinguishedName       = $_.DistinguishedName
                }
            }
        }
    }
    $FormattedComputers
}
function Get-WinADComputers {
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [switch] $PerDomain)
    $Today = Get-Date
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $Output = [ordered] @{}
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $Properties = @('DistinguishedName', 'LastLogonDate', 'PasswordLastSet', 'Enabled', 'DnsHostName', 'PasswordNeverExpires', 'PasswordNotRequired',
            'PasswordExpired', 'Manager', 'OperatingSystemVersion', 'OperatingSystem' , 'TrustedForDelegation', 'WhenCreated', 'WhenChanged', 'PrimaryGroupID')
        $Computers = Get-ADComputer -Filter * -Server $QueryServer -Properties $Properties
        $Output[$Domain] = foreach ($Computer in $Computers) {
            $ComputerLocation = ($Computer.DistinguishedName -split ',').Replace('OU=', '').Replace('CN=', '').Replace('DC=', '')
            $Region = $ComputerLocation[-4]
            $Country = $ComputerLocation[-5]
            if ($null -ne $Computer.LastLogonDate) { $LastLogonDays = "$(-$($Computer.LastLogonDate - $Today).Days)" } else { $LastLogonDays = $null }
            if ($null -ne $Computer.PasswordLastSet) { $PasswordLastChangedDays = "$(-$($Computer.PasswordLastSet - $Today).Days)" } else { $PasswordLastChangedDays = $null }
            [PSCustomObject] @{Name  = $Computer.Name
                SamAccountName       = $Computer.SamAccountName
                IsDC                 = if ($Computer.PrimaryGroupID -in 516, 521) { $true } else { $false }
                WhenChanged          = $Computer.WhenChanged
                Enabled              = $Computer.Enabled
                LastLogonDays        = $LastLogonDays
                PasswordLastDays     = $PasswordLastChangedDays
                Level0               = $Region
                Level1               = $Country
                OperatingSystem      = $Computer.OperatingSystem
                OperatingSystemName  = ConvertTo-OperatingSystem -OperatingSystem $Computer.OperatingSystem -OperatingSystemVersion $Computer.OperatingSystemVersion
                DistinguishedName    = $Computer.DistinguishedName
                LastLogonDate        = $Computer.LastLogonDate
                PasswordLastSet      = $Computer.PasswordLastSet
                PasswordNeverExpires = $Computer.PasswordNeverExpires
                PasswordNotRequired  = $Computer.PasswordNotRequired
                PasswordExpired      = $Computer.PasswordExpired
                ManagerDN            = $Computer.Manager
                Description          = $Computer.Description
                TrustedForDelegation = $Computer.TrustedForDelegation
            }
        }
    }
    if ($PerDomain) { $Output } else { $Output.Values }
}
function Get-WinADDelegatedAccounts {
    [CmdletBinding()]
    Param ([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended
    foreach ($Domain in $ForestInformation.Domains) {
        $SERVER_TRUST_ACCOUNT = 0x2000
        $TRUSTED_FOR_DELEGATION = 0x80000
        $TRUSTED_TO_AUTH_FOR_DELEGATION = 0x1000000
        $PARTIAL_SECRETS_ACCOUNT = 0x4000000
        $bitmask = $TRUSTED_FOR_DELEGATION -bor $TRUSTED_TO_AUTH_FOR_DELEGATION -bor $PARTIAL_SECRETS_ACCOUNT
        $filter = @"
(&
  (servicePrincipalname=*)
  (|
    (msDS-AllowedToActOnBehalfOfOtherIdentity=*)
    (msDS-AllowedToDelegateTo=*)
    (UserAccountControl:1.2.840.113556.1.4.804:=$bitmask)
  )
  (|
    (objectcategory=computer)
    (objectcategory=person)
    (objectcategory=msDS-GroupManagedServiceAccount)
    (objectcategory=msDS-ManagedServiceAccount)
  )
)
"@
 -replace "[\s\n]", ''
        $PropertyList = @('Enabled'
            "servicePrincipalname",
            "useraccountcontrol",
            "samaccountname",
            "msDS-AllowedToDelegateTo",
            "msDS-AllowedToActOnBehalfOfOtherIdentity"
            'IsCriticalSystemObject'
            'LastLogon'
            'PwdLastSet'
            'WhenChanged'
            'WhenCreated')
        try { $Accounts = Get-ADObject -LDAPFilter $filter -SearchBase $ForestInformation.DomainsExtended[$Domain].DistinguishedName -SearchScope Subtree -Properties $propertylist -Server $ForestInformation.QueryServers[$Domain].HostName[0] } catch {
            $Accounts = $null
            Write-Warning -Message "Get-WinADDelegatedAccounts - Failed to get information: $($_.Exception.Message)"
        }
        foreach ($Account in $Accounts) {
            $UAC = Convert-UserAccountControl -UserAccountControl $Account.useraccountcontrol
            $IsDC = ($Account.useraccountcontrol -band $SERVER_TRUST_ACCOUNT) -ne 0
            $FullDelegation = ($Account.useraccountcontrol -band $TRUSTED_FOR_DELEGATION) -ne 0
            $ConstrainedDelegation = ($Account.'msDS-AllowedToDelegateTo').count -gt 0
            $IsRODC = ($Account.useraccountcontrol -band $PARTIAL_SECRETS_ACCOUNT) -ne 0
            $ResourceDelegation = $null -ne $Account.'msDS-AllowedToActOnBehalfOfOtherIdentity'
            $PasswordLastSet = [datetime]::FromFileTimeUtc($Account.pwdLastSet)
            $LastLogonDate = [datetime]::FromFileTimeUtc($Account.LastLogon)
            [PSCustomobject] @{DomainName           = $Domain
                SamAccountName                      = $Account.samaccountname
                Enabled                             = $UAC -notcontains 'ACCOUNTDISABLE'
                ObjectClass                         = $Account.objectclass
                IsDC                                = $IsDC
                IsRODC                              = $IsRODC
                FullDelegation                      = $FullDelegation
                ConstrainedDelegation               = $ConstrainedDelegation
                ResourceDelegation                  = $ResourceDelegation
                LastLogonDate                       = $LastLogonDate
                PasswordLastSet                     = $PasswordLastSet
                UserAccountControl                  = $UAC
                WhenCreated                         = $Account.WhenCreated
                WhenChanged                         = $Account.WhenChanged
                IsCriticalSystemObject              = $Account.IsCriticalSystemObject
                AllowedToDelagateTo                 = $Account.'msDS-AllowedToDelegateTo'
                AllowedToActOnBehalfOfOtherIdentity = $Account.'msDS-AllowedToActOnBehalfOfOtherIdentity'
            }
        }
    }
}
function Get-WinADDFSHealth {
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [int] $EventDays = 1,
        [switch] $SkipGPO,
        [switch] $SkipAutodetection,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $Today = (Get-Date)
    $Yesterday = (Get-Date -Hour 0 -Second 0 -Minute 0 -Millisecond 0).AddDays(-$EventDays)
    if (-not $SkipAutodetection) { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation -Extended } else {
        if (-not $IncludeDomains) {
            Write-Warning "Get-WinADDFSHealth - You need to specify domain when using SkipAutodetection."
            return
        }
        $ForestInformation = @{Domains = $IncludeDomains
            DomainDomainControllers    = @{}
        }
        foreach ($Domain in $IncludeDomains) {
            $ForestInformation['DomainDomainControllers'][$Domain] = [System.Collections.Generic.List[Object]]::new()
            foreach ($DC in $IncludeDomainControllers) {
                try {
                    $DCInformation = Get-ADDomainController -Identity $DC -Server $Domain -ErrorAction Stop
                    Add-Member -InputObject $DCInformation -MemberType NoteProperty -Value $DCInformation.ComputerObjectDN -Name 'DistinguishedName' -Force
                    $ForestInformation['DomainDomainControllers'][$Domain].Add($DCInformation)
                } catch {
                    Write-Warning "Get-WinADDFSHealth - Can't get DC details. Skipping with error: $($_.Exception.Message)"
                    continue
                }
            }
        }
    }
    [Array] $Table = foreach ($Domain in $ForestInformation.Domains) {
        Write-Verbose "Get-WinADDFSHealth - Processing $Domain"
        [Array] $DomainControllersFull = $ForestInformation['DomainDomainControllers']["$Domain"]
        if ($DomainControllersFull.Count -eq 0) { continue }
        if (-not $SkipAutodetection) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] } else { $QueryServer = $DomainControllersFull[0].HostName }
        if (-not $SkipGPO) {
            try {
                $SystemsContainer = $ForestInformation['DomainsExtended'][$Domain].SystemsContainer
                if ($SystemsContainer) { $PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer) }
                [Array]$GPOs = Get-ADObject -ErrorAction Stop -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer -Properties Name, gPCFileSysPath, DisplayName, DistinguishedName, Description, Created, Modified, ObjectClass, ObjectGUID
            } catch { $GPOs = $null }
        }
        try {
            $CentralRepository = Get-ChildItem -Path "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" -ErrorAction Stop
            $CentralRepositoryDomain = if ($CentralRepository) { $true } else { $false }
        } catch { $CentralRepositoryDomain = $false }
        foreach ($DC in $DomainControllersFull) {
            Write-Verbose "Get-WinADDFSHealth - Processing $($DC.HostName) for $Domain"
            $DCName = $DC.Name
            $Hostname = $DC.Hostname
            $DN = $DC.DistinguishedName
            $LocalSettings = "CN=DFSR-LocalSettings,$DN"
            $Subscriber = "CN=Domain System Volume,$LocalSettings"
            $Subscription = "CN=SYSVOL Subscription,$Subscriber"
            $ReplicationStatus = @{'0' = 'Uninitialized'
                '1'                    = 'Initialized'
                '2'                    = 'Initial synchronization'
                '3'                    = 'Auto recovery'
                '4'                    = 'Normal'
                '5'                    = 'In error state'
                '6'                    = 'Disabled'
                '7'                    = 'Unknown'
            }
            $DomainSummary = [ordered] @{"DomainController" = $DCName
                "Domain"                                    = $Domain
                "Status"                                    = $false
                "ReplicationState"                          = 'Unknown'
                "IsPDC"                                     = $DC.OperationMasterRoles -contains 'PDCEmulator'
                'GroupPolicyOutput'                         = $null -ne $GPOs
                "GroupPolicyCount"                          = if ($GPOs) { $GPOs.Count } else { 0 }
                "SYSVOLCount"                               = 0
                'CentralRepository'                         = $CentralRepositoryDomain
                'CentralRepositoryDC'                       = $false
                'IdenticalCount'                            = $false
                "Availability"                              = $false
                "MemberReference"                           = $false
                "DFSErrors"                                 = 0
                "DFSEvents"                                 = $null
                "DFSLocalSetting"                           = $false
                "DomainSystemVolume"                        = $false
                "SYSVOLSubscription"                        = $false
                "StopReplicationOnAutoRecovery"             = $false
                "DFSReplicatedFolderInfo"                   = $null
            }
            if ($SkipGPO) {
                $DomainSummary.Remove('GroupPolicyOutput')
                $DomainSummary.Remove('GroupPolicyCount')
                $DomainSummary.Remove('SYSVOLCount')
            }
            $WarningVar = $null
            $DFSReplicatedFolderInfoAll = Get-CimData -NameSpace "root\microsoftdfs" -Class 'dfsrreplicatedfolderinfo' -ComputerName $Hostname -WarningAction SilentlyContinue -WarningVariable WarningVar -Verbose:$false
            $DFSReplicatedFolderInfo = $DFSReplicatedFolderInfoAll | Where-Object { $_.ReplicationGroupName -eq 'Domain System Volume' }
            if ($WarningVar) { $DomainSummary['ReplicationState'] = 'Unknown' } else { $DomainSummary['ReplicationState'] = $ReplicationStatus["$($DFSReplicatedFolderInfo.State)"] }
            try {
                $CentralRepositoryDC = Get-ChildItem -Path "\\$Hostname\SYSVOL\$Domain\policies\PolicyDefinitions" -ErrorAction Stop
                $DomainSummary['CentralRepositoryDC'] = if ($CentralRepositoryDC) { $true } else { $false }
            } catch { $DomainSummary['CentralRepositoryDC'] = $false }
            try {
                $MemberReference = (Get-ADObject -Identity $Subscriber -Properties msDFSR-MemberReference -Server $QueryServer -ErrorAction Stop).'msDFSR-MemberReference' -like "CN=$DCName,*"
                $DomainSummary['MemberReference'] = if ($MemberReference) { $true } else { $false }
            } catch { $DomainSummary['MemberReference'] = $false }
            try {
                $DFSLocalSetting = Get-ADObject -Identity $LocalSettings -Server $QueryServer -ErrorAction Stop
                $DomainSummary['DFSLocalSetting'] = if ($DFSLocalSetting) { $true } else { $false }
            } catch { $DomainSummary['DFSLocalSetting'] = $false }
            try {
                $DomainSystemVolume = Get-ADObject -Identity $Subscriber -Server $QueryServer -ErrorAction Stop
                $DomainSummary['DomainSystemVolume'] = if ($DomainSystemVolume) { $true } else { $false }
            } catch { $DomainSummary['DomainSystemVolume'] = $false }
            try {
                $SysVolSubscription = Get-ADObject -Identity $Subscription -Server $QueryServer -ErrorAction Stop
                $DomainSummary['SYSVOLSubscription'] = if ($SysVolSubscription) { $true } else { $false }
            } catch { $DomainSummary['SYSVOLSubscription'] = $false }
            if (-not $SkipGPO) {
                try {
                    [Array] $SYSVOL = Get-ChildItem -Path "\\$Hostname\SYSVOL\$Domain\Policies" -Exclude "PolicyDefinitions*" -ErrorAction Stop
                    $DomainSummary['SysvolCount'] = $SYSVOL.Count
                } catch { $DomainSummary['SysvolCount'] = 0 }
            }
            if (Test-Connection $Hostname -ErrorAction SilentlyContinue) { $DomainSummary['Availability'] = $true } else { $DomainSummary['Availability'] = $false }
            try {
                [Array] $Events = Get-Events -LogName "DFS Replication" -Level Error -ComputerName $Hostname -DateFrom $Yesterday -DateTo $Today
                $DomainSummary['DFSErrors'] = $Events.Count
                $DomainSummary['DFSEvents'] = $Events
            } catch { $DomainSummary['DFSErrors'] = $null }
            $DomainSummary['IdenticalCount'] = $DomainSummary['GroupPolicyCount'] -eq $DomainSummary['SYSVOLCount']
            try { $Registry = Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\DFSR\Parameters" -ComputerName $Hostname -ErrorAction Stop } catch { $Registry = $null }
            if ($null -ne $Registry.StopReplicationOnAutoRecovery) { $DomainSummary['StopReplicationOnAutoRecovery'] = [bool] $Registry.StopReplicationOnAutoRecovery } else { $DomainSummary['StopReplicationOnAutoRecovery'] = $null }
            $DomainSummary['DFSReplicatedFolderInfo'] = $DFSReplicatedFolderInfoAll
            $All = @(if (-not $SkipGPO) { $DomainSummary['GroupPolicyOutput'] }
                $DomainSummary['SYSVOLSubscription']
                $DomainSummary['ReplicationState'] -eq 'Normal'
                $DomainSummary['DomainSystemVolume']
                $DomainSummary['DFSLocalSetting']
                $DomainSummary['MemberReference']
                $DomainSummary['Availability']
                $DomainSummary['IdenticalCount']
                $DomainSummary['DFSErrors'] -eq 0)
            $DomainSummary['Status'] = $All -notcontains $false
            [PSCustomObject] $DomainSummary
        }
    }
    $Table
}
function Get-WinADDHCP {
    [cmdletBinding()]
    param()
    $ForestDomainControllers = Get-WinADForestControllers
    try { $DHCPs = Get-DhcpServerInDC -Verbose } catch {
        Write-Warning -Message "Get-WinADDHCP - Couldn't get DHCP data from AD: $($_.Exception.Message)"
        return
    }
    $CacheDHCP = @{}
    $CacheAD = [ordered] @{}
    foreach ($DHCP in $DHCPs) { $CacheDHCP[$DHCP.DNSName] = $DHCP }
    foreach ($DC in $ForestDomainControllers) { $CacheAD[$DC.HostName] = $DC }
    foreach ($DHCP in $DHCPs) {
        $DHCPObject = [ordered] @{DNSName = $DHCP.DNSName
            IPAddress                     = $DHCP.IPAddress
        }
        if ($CacheAD[$DHCP.DNSName]) {
            $DHCPObject['IsDC'] = $true
            $DHCPObject['IsRODC'] = $CacheAD[$DHCP.DNSName].IsReadOnly
            $DHCPObject['IsGlobalCatalog'] = $CacheAD[$DHCP.DNSName].IsGlobalCatalog
            $DHCPObject['DCIPv4'] = $CacheAD[$DHCP.DNSName].IPV4Address
            $DHCPObject['DCIPv6'] = $CacheAD[$DHCP.DNSName].IPV6Address
        } else {
            $DHCPObject['IsDC'] = $false
            $DHCPObject['IsRODC'] = $false
            $DHCPObject['IsGlobalCatalog'] = $false
            $DHCPObject['DCIPv4'] = $null
            $DHCPObject['DCIPv6'] = $null
        }
        $DNS = Resolve-DnsName -Name $DHCP.DNSName -ErrorAction SilentlyContinue
        if ($DNS) {
            $DHCPObject['IsInDNS'] = $true
            $DHCPObject['DNSType'] = $DNS.Type
        } else {
            $DHCPObject['IsInDNS'] = $false
            $DHCPObject['DNSType'] = $null
        }
        [PSCustomObject] $DHCPObject
    }
}
function Get-WinADDiagnostics {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $LevelsDictionary = @{'0' = 'None'
        '1'                   = 'Minimal'
        '2'                   = 'Basic'
        '3'                   = 'Extensive'
        '4'                   = 'Verbose'
        '5'                   = 'Internal'
        ''                    = 'Unknown'
    }
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    [Array] $Computers = $ForestInformation.ForestDomainControllers.HostName
    foreach ($Computer in $Computers) {
        try { $Output = Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' -ComputerName $Computer -Verbose:$false -ErrorAction Stop } catch {
            $ErrorMessage1 = $_.Exception.Message
            $Output = $null
        }
        try {
            $Output1 = Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -ComputerName $Computer -Verbose:$false -ErrorAction Stop
            if ($Output1.DbFlag -eq 545325055) { $Netlogon = $true } else { $Netlogon = $false }
        } catch {
            $ErrorMessage2 = $_.Exception.Message
            $Netlogon = 'Unknown'
        }
        if (-not $ErrorMessage1 -and -not $ErrorMessage2) {
            $Comment = 'OK'
            [PSCustomObject] @{'ComputerName'         = $Computer
                'Knowledge Consistency Checker (KCC)' = $LevelsDictionary["$($Output.'1 Knowledge Consistency Checker')"]
                'Security Events'                     = $LevelsDictionary["$($Output.'2 Security Events')"]
                'ExDS Interface Events'               = $LevelsDictionary["$($Output.'3 ExDS Interface Events')"]
                'MAPI Interface Events'               = $LevelsDictionary["$($Output.'4 MAPI Interface Events')"]
                'Replication Events'                  = $LevelsDictionary["$($Output.'5 Replication Events')"]
                'Garbage Collection'                  = $LevelsDictionary["$($Output.'6 Garbage Collection')"]
                'Internal Configuration'              = $LevelsDictionary["$($Output.'7 Internal Configuration')"]
                'Directory Access'                    = $LevelsDictionary["$($Output.'8 Directory Access')"]
                'Internal Processing'                 = $LevelsDictionary["$($Output.'9 Internal Processing')"]
                'Performance Counters'                = $LevelsDictionary["$($Output.'10 Performance Counters')"]
                'Initialization / Termination'        = $LevelsDictionary["$($Output.'11 Initialization/Termination')"]
                'Service Control'                     = $LevelsDictionary["$($Output.'12 Service Control')"]
                'Name Resolution'                     = $LevelsDictionary["$($Output.'13 Name Resolution')"]
                'Backup'                              = $LevelsDictionary["$($Output.'14 Backup')"]
                'Field Engineering'                   = $LevelsDictionary["$($Output.'15 Field Engineering')"]
                'LDAP Interface Events'               = $LevelsDictionary["$($Output.'16 LDAP Interface Events')"]
                'Setup'                               = $LevelsDictionary["$($Output.'17 Setup')"]
                'Global Catalog'                      = $LevelsDictionary["$($Output.'18 Global Catalog')"]
                'Inter-site Messaging'                = $LevelsDictionary["$($Output.'19 Inter-site Messaging')"]
                'Group Caching'                       = $LevelsDictionary["$($Output.'20 Group Caching')"]
                'Linked-Value Replication'            = $LevelsDictionary["$($Output.'21 Linked-Value Replication')"]
                'DS RPC Client'                       = $LevelsDictionary["$($Output.'22 DS RPC Client')"]
                'DS RPC Server'                       = $LevelsDictionary["$($Output.'23 DS RPC Server')"]
                'DS Schema'                           = $LevelsDictionary["$($Output.'24 DS Schema')"]
                'Transformation Engine'               = $LevelsDictionary["$($Output.'25 Transformation Engine')"]
                'Claims-Based Access Control'         = $LevelsDictionary["$($Output.'26 Claims-Based Access Control')"]
                'Netlogon'                            = $Netlogon
                'Comment'                             = $Comment
            }
        } else {
            $Comment = $ErrorMessage1 + ' ' + $ErrorMessage2
            [PSCustomObject] @{'ComputerName'         = $Computer
                'Knowledge Consistency Checker (KCC)' = $LevelsDictionary["$($Output.'1 Knowledge Consistency Checker')"]
                'Security Events'                     = $LevelsDictionary["$($Output.'2 Security Events')"]
                'ExDS Interface Events'               = $LevelsDictionary["$($Output.'3 ExDS Interface Events')"]
                'MAPI Interface Events'               = $LevelsDictionary["$($Output.'4 MAPI Interface Events')"]
                'Replication Events'                  = $LevelsDictionary["$($Output.'5 Replication Events')"]
                'Garbage Collection'                  = $LevelsDictionary["$($Output.'6 Garbage Collection')"]
                'Internal Configuration'              = $LevelsDictionary["$($Output.'7 Internal Configuration')"]
                'Directory Access'                    = $LevelsDictionary["$($Output.'8 Directory Access')"]
                'Internal Processing'                 = $LevelsDictionary["$($Output.'9 Internal Processing')"]
                'Performance Counters'                = $LevelsDictionary["$($Output.'10 Performance Counters')"]
                'Initialization / Termination'        = $LevelsDictionary["$($Output.'11 Initialization/Termination')"]
                'Service Control'                     = $LevelsDictionary["$($Output.'12 Service Control')"]
                'Name Resolution'                     = $LevelsDictionary["$($Output.'13 Name Resolution')"]
                'Backup'                              = $LevelsDictionary["$($Output.'14 Backup')"]
                'Field Engineering'                   = $LevelsDictionary["$($Output.'15 Field Engineering')"]
                'LDAP Interface Events'               = $LevelsDictionary["$($Output.'16 LDAP Interface Events')"]
                'Setup'                               = $LevelsDictionary["$($Output.'17 Setup')"]
                'Global Catalog'                      = $LevelsDictionary["$($Output.'18 Global Catalog')"]
                'Inter-site Messaging'                = $LevelsDictionary["$($Output.'19 Inter-site Messaging')"]
                'Group Caching'                       = $LevelsDictionary["$($Output.'20 Group Caching')"]
                'Linked-Value Replication'            = $LevelsDictionary["$($Output.'21 Linked-Value Replication')"]
                'DS RPC Client'                       = $LevelsDictionary["$($Output.'22 DS RPC Client')"]
                'DS RPC Server'                       = $LevelsDictionary["$($Output.'23 DS RPC Server')"]
                'DS Schema'                           = $LevelsDictionary["$($Output.'24 DS Schema')"]
                'Transformation Engine'               = $LevelsDictionary["$($Output.'25 Transformation Engine')"]
                'Claims-Based Access Control'         = $LevelsDictionary["$($Output.'26 Claims-Based Access Control')"]
                'Netlogon'                            = $Netlogon
                'Comment'                             = $Comment
            }
        }
    }
}
function Get-WinADDomain {
    [cmdletBinding()]
    param([string] $Domain)
    try {
        if ($Domain) {
            $Type = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain
            $Context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new($Type, $Domain)
            $DomainInformation = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($Context)
        } else { $DomainInformation = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain() }
    } catch { Write-Warning "Get-WinADDomain - Can't get $Domain information, error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" }
    $DomainInformation
}
Function Get-WinADDuplicateObject {
    [alias('Get-WinADForestObjectsConflict')]
    [CmdletBinding()]
    Param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string] $PartialMatchDistinguishedName,
        [string[]] $IncludeObjectClass,
        [string[]] $ExcludeObjectClass,
        [switch] $Extended,
        [switch] $NoPostProcessing)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $DC = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $getADObjectSplat = @{LDAPFilter = "(|(cn=*\0ACNF:*)(ou=*CNF:*))"
            Properties                   = 'DistinguishedName', 'ObjectClass', 'DisplayName', 'SamAccountName', 'Name', 'ObjectCategory', 'WhenCreated', 'WhenChanged', 'ProtectedFromAccidentalDeletion', 'ObjectGUID'
            Server                       = $DC
            SearchScope                  = 'Subtree'
        }
        $Objects = Get-ADObject @getADObjectSplat
        foreach ($_ in $Objects) {
            if ($ExcludeObjectClass) { if ($ExcludeObjectClass -contains $_.ObjectClass) { continue } }
            if ($IncludeObjectClass) { if ($IncludeObjectClass -notcontains $_.ObjectClass) { continue } }
            if ($PartialMatchDistinguishedName) { if ($_.DistinguishedName -notlike $PartialMatchDistinguishedName) { continue } }
            if ($NoPostProcessing) {
                $_
                continue
            }
            $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToDomainCN
            $ConflictObject = [ordered] @{ConflictDN = $_.DistinguishedName
                ConflictWhenChanged                  = $_.WhenChanged
                DomainName                           = $DomainName
                ObjectClass                          = $_.ObjectClass
            }
            $LiveObjectData = [ordered] @{LiveDn = "N/A"
                LiveWhenChanged                  = "N/A"
            }
            $RestData = [ordered] @{DisplayName = $_.DisplayName
                Name                            = $_.Name.Replace("`n", ' ')
                SamAccountName                  = $_.SamAccountName
                ObjectCategory                  = $_.ObjectCategory
                WhenCreated                     = $_.WhenCreated
                WhenChanged                     = $_.WhenChanged
                ProtectedFromAccidentalDeletion = $_.ProtectedFromAccidentalDeletion
                ObjectGUID                      = $_.ObjectGUID.Guid
            }
            if ($Extended) {
                $LiveObject = $null
                $ConflictObject = $ConflictObject + $LiveObjectData + $RestData
                if (Select-String -SimpleMatch "\0ACNF:" -InputObject $ConflictObject.ConflictDn) {
                    $SplitConfDN = $ConflictObject.ConflictDn -split "0ACNF:"
                    try { $LiveObject = Get-ADObject -Identity "$($SplitConfDN[0].TrimEnd("\"))$($SplitConfDN[1].Substring(36))" -Properties WhenChanged -Server $DC -ErrorAction Stop } catch {}
                    if ($LiveObject) {
                        $ConflictObject.LiveDN = $LiveObject.DistinguishedName
                        $ConflictObject.LiveWhenChanged = $LiveObject.WhenChanged
                    }
                } else {
                    $SplitConfDN = $ConflictObject.ConflictDn -split "CNF:"
                    try { $LiveObject = Get-ADObject -Identity "$($SplitConfDN[0])$($SplitConfDN[1].Substring(36))" -Properties WhenChanged -Server $DC -ErrorAction Stop } catch {}
                    if ($LiveObject) {
                        $ConflictObject.LiveDN = $LiveObject.DistinguishedName
                        $ConflictObject.LiveWhenChanged = $LiveObject.WhenChanged
                    }
                }
            } else { $ConflictObject = $ConflictObject + $RestData }
            [PSCustomObject] $ConflictObject
        }
    }
}
function Get-WinADForest {
    [cmdletBinding()]
    param([string] $Forest)
    try {
        if ($Forest) {
            $Type = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest
            $Context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new($Type, $Forest)
            $ForestInformation = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($Context)
        } else { $ForestInformation = ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()) }
    } catch { Write-Warning "Get-WinADForest - Can't get $Forest information, error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" }
    $ForestInformation
}
function Get-WinADForestControllerInformation {
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $Today = Get-Date
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Verbose:$false
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
        $Properties = @('PrimaryGroupID'
            'PrimaryGroup'
            'Enabled'
            'ManagedBy'
            'OperatingSystem'
            'OperatingSystemVersion'
            'PasswordLastSet'
            'PasswordExpired'
            'PasswordNeverExpires'
            'PasswordNotRequired'
            'TrustedForDelegation'
            'UseDESKeyOnly'
            'TrustedToAuthForDelegation'
            'WhenCreated'
            'WhenChanged'
            'LastLogonDate'
            'IPv4Address'
            'IPv6Address')
        $DCs = Get-ADComputer -Server $QueryServer -SearchBase $ForestInformation['DomainsExtended'][$Domain].DomainControllersContainer -Filter * -Properties $Properties
        $Count = 0
        foreach ($DC in $DCs) {
            $Count++
            Write-Verbose -Message "Get-WinADForestControllerInformation - Processing [$($Domain)]($Count/$($DCs.Count)) $($DC.DNSHostName)"
            $Owner = Get-ADACLOwner -ADObject $DC.DistinguishedName -Resolve
            if ($null -ne $DC.LastLogonDate) { [int] $LastLogonDays = "$(-$($DC.LastLogonDate - $Today).Days)" } else { $LastLogonDays = $null }
            if ($null -ne $DC.PasswordLastSet) { [int] $PasswordLastChangedDays = "$(-$($DC.PasswordLastSet - $Today).Days)" } else { $PasswordLastChangedDays = $null }
            $DNS = Resolve-DnsName -DnsOnly -Name $DC.DNSHostName -ErrorAction SilentlyContinue -QuickTimeout -Verbose:$false
            if ($DNS) {
                $ResolvedIP4 = ($DNS | Where-Object { $_.Type -eq 'A' }).IPAddress
                $ResolvedIP6 = ($DNS | Where-Object { $_.Type -eq 'AAAA' }).IPAddress
                $DNSStatus = $true
            } else {
                $ResolvedIP4 = $null
                $ResolvedIP6 = $null
                $DNSStatus = $false
            }
            [PSCustomObject] @{DNSHostName = $DC.DNSHostName
                DomainName                 = $Domain
                Enabled                    = $DC.Enabled
                DNSStatus                  = $DNSStatus
                IPAddressStatusV4          = if ($ResolvedIP4 -eq $DC.IPv4Address) { $true } else { $false }
                IPAddressStatusV6          = if ($ResolvedIP6 -eq $DC.IPv6Address) { $true } else { $false }
                IPAddressHasOneIpV4        = $ResolvedIP4 -isnot [Array]
                IPAddressHasOneipV6        = $ResolvedIP6 -isnot [Array]
                ManagerNotSet              = $Null -eq $ManagedBy
                OwnerType                  = $Owner.OwnerType
                PasswordLastChangedDays    = $PasswordLastChangedDays
                LastLogonDays              = $LastLogonDays
                Owner                      = $Owner.OwnerName
                OwnerSid                   = $Owner.OwnerSid
                ManagedBy                  = $DC.ManagedBy
                DNSResolvedIPv4            = $ResolvedIP4
                DNSResolvedIPv6            = $ResolvedIP6
                IPv4Address                = $DC.IPv4Address
                IPv6Address                = $DC.IPv6Address
                LastLogonDate              = $DC.LastLogonDate
                OperatingSystem            = $DC.OperatingSystem
                OperatingSystemVersion     = $DC.OperatingSystemVersion
                PasswordExpired            = $DC.PasswordExpired
                PasswordLastSet            = $DC.PasswordLastSet
                PasswordNeverExpires       = $DC.PasswordNeverExpires
                PasswordNotRequired        = $DC.PasswordNotRequired
                TrustedForDelegation       = $DC.TrustedForDelegation
                TrustedToAuthForDelegation = $DC.TrustedToAuthForDelegation
                UseDESKeyOnly              = $DC.UseDESKeyOnly
                WhenCreated                = $DC.WhenCreated
                WhenChanged                = $DC.WhenChanged
                DistinguishedName          = $DC.DistinguishedName
            }
        }
    }
}
function Get-WinADForestOptionalFeatures {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [Array] $ComputerProperties,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    if (-not $ComputerProperties) { $ComputerProperties = Get-WinADForestSchemaProperties -Schema 'Computers' -Forest $Forest -ExtendedForestInformation $ForestInformation }
    $QueryServer = $ForestInformation['QueryServers']["Forest"].HostName[0]
    $LapsProperties = 'ms-Mcs-AdmPwd'
    $OptionalFeatures = $(Get-ADOptionalFeature -Filter * -Server $QueryServer)
    $Optional = [ordered]@{'Recycle Bin Enabled'       = $false
        'Privileged Access Management Feature Enabled' = $false
        'Laps Enabled'                                 = ($ComputerProperties.Name -contains $LapsProperties)
    }
    foreach ($Feature in $OptionalFeatures) {
        if ($Feature.Name -eq 'Recycle Bin Feature') { $Optional.'Recycle Bin Enabled' = $Feature.EnabledScopes.Count -gt 0 }
        if ($Feature.Name -eq 'Privileged Access Management Feature') { $Optional.'Privileged Access Management Feature Enabled' = $Feature.EnabledScopes.Count -gt 0 }
    }
    $Optional
}
function Get-WinADForestReplication {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [switch] $Extended,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ProcessErrors = [System.Collections.Generic.List[PSCustomObject]]::new()
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    $Replication = foreach ($DC in $ForestInformation.ForestDomainControllers) {
        try { Get-ADReplicationPartnerMetadata -Target $DC.HostName -Partition * -ErrorAction Stop } catch {
            Write-Warning -Message "Get-WinADForestReplication - Error on server $($_.Exception.ServerName): $($_.Exception.Message)"
            $ProcessErrors.Add([PSCustomObject] @{Server = $_.Exception.ServerName; StatusMessage = $_.Exception.Message })
        }
    }
    foreach ($_ in $Replication) {
        $ServerPartner = (Resolve-DnsName -Name $_.PartnerAddress -Verbose:$false -ErrorAction SilentlyContinue)
        $ServerInitiating = (Resolve-DnsName -Name $_.Server -Verbose:$false -ErrorAction SilentlyContinue)
        $ReplicationObject = [ordered] @{Server = $_.Server
            ServerIPV4                          = $ServerInitiating.IP4Address
            ServerPartner                       = $ServerPartner.NameHost
            ServerPartnerIPV4                   = $ServerPartner.IP4Address
            LastReplicationAttempt              = $_.LastReplicationAttempt
            LastReplicationResult               = $_.LastReplicationResult
            LastReplicationSuccess              = $_.LastReplicationSuccess
            ConsecutiveReplicationFailures      = $_.ConsecutiveReplicationFailures
            LastChangeUsn                       = $_.LastChangeUsn
            PartnerType                         = $_.PartnerType
            Partition                           = $_.Partition
            TwoWaySync                          = $_.TwoWaySync
            ScheduledSync                       = $_.ScheduledSync
            SyncOnStartup                       = $_.SyncOnStartup
            CompressChanges                     = $_.CompressChanges
            DisableScheduledSync                = $_.DisableScheduledSync
            IgnoreChangeNotifications           = $_.IgnoreChangeNotifications
            IntersiteTransport                  = $_.IntersiteTransport
            IntersiteTransportGuid              = $_.IntersiteTransportGuid
            IntersiteTransportType              = $_.IntersiteTransportType
            UsnFilter                           = $_.UsnFilter
            Writable                            = $_.Writable
            Status                              = if ($_.LastReplicationResult -ne 0) { $false } else { $true }
            StatusMessage                       = "Last successful replication time was $($_.LastReplicationSuccess), Consecutive Failures: $($_.ConsecutiveReplicationFailures)"
        }
        if ($Extended) {
            $ReplicationObject.Partner = $_.Partner
            $ReplicationObject.PartnerAddress = $_.PartnerAddress
            $ReplicationObject.PartnerGuid = $_.PartnerGuid
            $ReplicationObject.PartnerInvocationId = $_.PartnerInvocationId
            $ReplicationObject.PartitionGuid = $_.PartitionGuid
        }
        [PSCustomObject] $ReplicationObject
    }
    foreach ($_ in $ProcessErrors) {
        if ($null -ne $_.Server) { $ServerInitiating = (Resolve-DnsName -Name $_.Server -Verbose:$false -ErrorAction SilentlyContinue) } else { $ServerInitiating = [PSCustomObject] @{IP4Address = '127.0.0.1' } }
        $ReplicationObject = [ordered] @{Server = $_.Server
            ServerIPV4                          = $ServerInitiating.IP4Address
            ServerPartner                       = 'Unknown'
            ServerPartnerIPV4                   = '127.0.0.1'
            LastReplicationAttempt              = $null
            LastReplicationResult               = $null
            LastReplicationSuccess              = $null
            ConsecutiveReplicationFailures      = $null
            LastChangeUsn                       = $null
            PartnerType                         = $null
            Partition                           = $null
            TwoWaySync                          = $null
            ScheduledSync                       = $null
            SyncOnStartup                       = $null
            CompressChanges                     = $null
            DisableScheduledSync                = $null
            IgnoreChangeNotifications           = $null
            IntersiteTransport                  = $null
            IntersiteTransportGuid              = $null
            IntersiteTransportType              = $null
            UsnFilter                           = $null
            Writable                            = $null
            Status                              = $false
            StatusMessage                       = $_.StatusMessage
        }
        if ($Extended) {
            $ReplicationObject.Partner = $null
            $ReplicationObject.PartnerAddress = $null
            $ReplicationObject.PartnerGuid = $null
            $ReplicationObject.PartnerInvocationId = $null
            $ReplicationObject.PartitionGuid = $null
        }
        [PSCustomObject] $ReplicationObject
    }
}
function Get-WinADForestRoles {
    [alias('Get-WinADRoles', 'Get-WinADDomainRoles')]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [switch] $Formatted,
        [string] $Splitter = ', ',
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    $Roles = [ordered] @{SchemaMaster = $null
        DomainNamingMaster            = $null
        PDCEmulator                   = $null
        RIDMaster                     = $null
        InfrastructureMaster          = $null
        IsReadOnly                    = $null
        IsGlobalCatalog               = $null
    }
    foreach ($_ in $ForestInformation.ForestDomainControllers) {
        if ($_.IsSchemaMaster -eq $true) { $Roles['SchemaMaster'] = if ($null -ne $Roles['SchemaMaster']) { @($Roles['SchemaMaster']) + $_.HostName } else { $_.HostName } }
        if ($_.IsDomainNamingMaster -eq $true) { $Roles['DomainNamingMaster'] = if ($null -ne $Roles['DomainNamingMaster']) { @($Roles['DomainNamingMaster']) + $_.HostName } else { $_.HostName } }
        if ($_.IsPDC -eq $true) { $Roles['PDCEmulator'] = if ($null -ne $Roles['PDCEmulator']) { @($Roles['PDCEmulator']) + $_.HostName } else { $_.HostName } }
        if ($_.IsRIDMaster -eq $true) { $Roles['RIDMaster'] = if ($null -ne $Roles['RIDMaster']) { @($Roles['RIDMaster']) + $_.HostName } else { $_.HostName } }
        if ($_.IsInfrastructureMaster -eq $true) { $Roles['InfrastructureMaster'] = if ($null -ne $Roles['InfrastructureMaster']) { @($Roles['InfrastructureMaster']) + $_.HostName } else { $_.HostName } }
        if ($_.IsReadOnly -eq $true) { $Roles['IsReadOnly'] = if ($null -ne $Roles['IsReadOnly']) { @($Roles['IsReadOnly']) + $_.HostName } else { $_.HostName } }
        if ($_.IsGlobalCatalog -eq $true) { $Roles['IsGlobalCatalog'] = if ($null -ne $Roles['IsGlobalCatalog']) { @($Roles['IsGlobalCatalog']) + $_.HostName } else { $_.HostName } }
    }
    if ($Formatted) { foreach ($_ in ([string[]] $Roles.Keys)) { $Roles[$_] = $Roles[$_] -join $Splitter } }
    $Roles
}
function Get-WinADForestSchemaProperties {
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [validateSet('Computers', 'Users')][string[]] $Schema = @('Computers', 'Users'),
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    if ($Forest) {
        $Type = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest
        $Context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new($Type, $ForestInformation.Forest)
        $CurrentSchema = [directoryservices.activedirectory.activedirectoryschema]::GetSchema($Context)
    } else { $CurrentSchema = [directoryservices.activedirectory.activedirectoryschema]::GetCurrentSchema() }
    if ($Schema -contains 'Computers') {
        $CurrentSchema.FindClass("computer").mandatoryproperties | Select-Object -Property name, commonname, description, syntax , SchemaGuid
        $CurrentSchema.FindClass("computer").optionalproperties | Select-Object -Property name, commonname, description, syntax, SchemaGuid
    }
    if ($Schema -contains 'Users') {
        $CurrentSchema.FindClass("user").mandatoryproperties | Select-Object -Property name, commonname, description, syntax, SchemaGuid
        $CurrentSchema.FindClass("user").optionalproperties | Select-Object -Property name, commonname, description, syntax, SchemaGuid
    }
}
function Get-WinADForestSites {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [switch] $Formatted,
        [string] $Splitter,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0]
    $Sites = Get-ADReplicationSite -Filter * -Properties * -Server $QueryServer
    foreach ($Site in $Sites) {
        [Array] $DCs = $ForestInformation.ForestDomainControllers | Where-Object { $_.Site -eq $Site.Name }
        [Array] $Subnets = ConvertFrom-DistinguishedName -DistinguishedName $Site.'Subnets'
        if ($Formatted) {
            [PSCustomObject] @{'Name'                                     = $Site.Name
                'Description'                                             = $Site.'Description'
                'CanonicalName'                                           = $Site.'CanonicalName'
                'Subnets Count'                                           = $Subnets.Count
                'Domain Controllers Count'                                = $DCs.Count
                'Location'                                                = $Site.'Location'
                'ManagedBy'                                               = $Site.'ManagedBy'
                'Subnets'                                                 = if ($Splitter) { $Subnets -join $Splitter } else { $Subnets }
                'Domain Controllers'                                      = if ($Splitter) { ($DCs).HostName -join $Splitter } else { ($DCs).HostName }
                'DistinguishedName'                                       = $Site.'DistinguishedName'
                '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'
                'sDRightsEffective'                                       = $Site.'sDRightsEffective'
                'Topology Cleanup Enabled'                                = $Site.'TopologyCleanupEnabled'
                'Topology Detect Stale Enabled'                           = $Site.'TopologyDetectStaleEnabled'
                'Topology Minimum Hops Enabled'                           = $Site.'TopologyMinimumHopsEnabled'
                'Universal Group Caching Enabled'                         = $Site.'UniversalGroupCachingEnabled'
                'Universal Group Caching Refresh Site'                    = $Site.'UniversalGroupCachingRefreshSite'
                'Windows Server 2000 Bridgehead Selection Method Enabled' = $Site.'WindowsServer2000BridgeheadSelectionMethodEnabled'
                'Windows Server 2000 KCC ISTG Selection Behavior Enabled' = $Site.'WindowsServer2000KCCISTGSelectionBehaviorEnabled'
                'Windows Server 2003 KCC Behavior Enabled'                = $Site.'WindowsServer2003KCCBehaviorEnabled'
                'Windows Server 2003 KCC Ignore Schedule Enabled'         = $Site.'WindowsServer2003KCCIgnoreScheduleEnabled'
                'Windows Server 2003 KCC SiteLink Bridging Enabled'       = $Site.'WindowsServer2003KCCSiteLinkBridgingEnabled'
                'Created'                                                 = $Site.Created
                'Modified'                                                = $Site.Modified
                'Deleted'                                                 = $Site.Deleted
            }
        } else {
            [PSCustomObject] @{'Name'                               = $Site.Name
                'Description'                                       = $Site.'Description'
                'CanonicalName'                                     = $Site.'CanonicalName'
                'SubnetsCount'                                      = $Subnets.Count
                'DomainControllersCount'                            = $DCs.Count
                'Subnets'                                           = if ($Splitter) { $Subnets -join $Splitter } else { $Subnets }
                'DomainControllers'                                 = if ($Splitter) { ($DCs).HostName -join $Splitter } else { ($DCs).HostName }
                'Location'                                          = $Site.'Location'
                'ManagedBy'                                         = $Site.'ManagedBy'
                'DistinguishedName'                                 = $Site.'DistinguishedName'
                'ProtectedFromAccidentalDeletion'                   = $Site.'ProtectedFromAccidentalDeletion'
                'RedundantServerTopologyEnabled'                    = $Site.'RedundantServerTopologyEnabled'
                'AutomaticInterSiteTopologyGenerationEnabled'       = $Site.'AutomaticInterSiteTopologyGenerationEnabled'
                'AutomaticTopologyGenerationEnabled'                = $Site.'AutomaticTopologyGenerationEnabled'
                'sDRightsEffective'                                 = $Site.'sDRightsEffective'
                'TopologyCleanupEnabled'                            = $Site.'TopologyCleanupEnabled'
                'TopologyDetectStaleEnabled'                        = $Site.'TopologyDetectStaleEnabled'
                'TopologyMinimumHopsEnabled'                        = $Site.'TopologyMinimumHopsEnabled'
                'UniversalGroupCachingEnabled'                      = $Site.'UniversalGroupCachingEnabled'
                'UniversalGroupCachingRefreshSite'                  = $Site.'UniversalGroupCachingRefreshSite'
                'WindowsServer2000BridgeheadSelectionMethodEnabled' = $Site.'WindowsServer2000BridgeheadSelectionMethodEnabled'
                'WindowsServer2000KCCISTGSelectionBehaviorEnabled'  = $Site.'WindowsServer2000KCCISTGSelectionBehaviorEnabled'
                'WindowsServer2003KCCBehaviorEnabled'               = $Site.'WindowsServer2003KCCBehaviorEnabled'
                'WindowsServer2003KCCIgnoreScheduleEnabled'         = $Site.'WindowsServer2003KCCIgnoreScheduleEnabled'
                'WindowsServer2003KCCSiteLinkBridgingEnabled'       = $Site.'WindowsServer2003KCCSiteLinkBridgingEnabled'
                'Created'                                           = $Site.Created
                'Modified'                                          = $Site.Modified
                'Deleted'                                           = $Site.Deleted
            }
        }
    }
}
function Get-WinADForestSubnet {
    [alias('Get-WinADSubnet')]
    [cmdletBinding()]
    param([string] $Forest,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [switch] $VerifyOverlap)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0]
    $ForestDN = ConvertTo-DistinguishedName -ToDomain -CanonicalName $ForestInformation.Forest.Name
    $ADObjectSplat = @{Server = $QueryServer
        LDAPFilter            = '(objectClass=subnet)'
        SearchBase            = "CN=Subnets,CN=Sites,CN=Configuration,$($($ForestDN))"
        SearchScope           = 'OneLevel'
        Properties            = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description'
    }
    try { $SubnetsList = Get-ADObject @ADObjectSplat -ErrorAction Stop } catch { Write-Warning "Get-WinADSites - LDAP Filter: $($ADObjectSplat.LDAPFilter), SearchBase: $($ADObjectSplat.SearchBase)), Error: $($_.Exception.Message)" }
    $Cache = @{}
    if ($VerifyOverlap) {
        $Subnets = Get-ADSubnet -Subnets $SubnetsList -AsHashTable
        $OverlappingSubnets = Test-ADSubnet -Subnets $Subnets
        foreach ($Subnet in $OverlappingSubnets) {
            if (-not $Cache[$Subnet.Name]) { $Cache[$Subnet.Name] = [System.Collections.Generic.List[string]]::new() }
            $Cache[$Subnet.Name].Add($Subnet.OverlappingSubnet)
        }
        foreach ($Subnet in $Subnets) {
            if ($Subnet.Type -eq 'IPv4') { $Subnet['Overlap'] = $false }
            if ($Cache[$Subnet.Name]) {
                $Subnet['Overlap'] = $true
                $Subnet['OverLapList'] = $Cache[$Subnet.Name]
            } else {}
            [PSCustomObject] $Subnet
        }
    } else { Get-ADSubnet -Subnets $Subnets }
}
function Get-WinADGroupMember {
    <#
    .SYNOPSIS
    The Get-WinADGroupMember cmdlet gets the members of an Active Directory group. Members can be users, groups, and computers.
 
    .DESCRIPTION
    The Get-WinADGroupMember cmdlet gets the members of an Active Directory group. Members can be users, groups, and computers. The Identity parameter specifies the Active Directory group to access. You can identify a group by its distinguished name, GUID, security identifier, or Security Account Manager (SAM) account name. You can also specify the group by passing a group object through the pipeline. For example, you can use the Get-ADGroup cmdlet to get a group object and then pass the object through the pipeline to the Get-WinADGroupMember cmdlet.
 
    .PARAMETER Identity
    Specifies an Active Directory group object
 
    .PARAMETER AddSelf
    Adds details about initial group name to output
 
    .PARAMETER SelfOnly
    Returns only one object that's summary for the whole group
 
    .PARAMETER AdditionalStatistics
    Adds additional data to Self object (when AddSelf is used). This data is available always if SelfOnly is used. It includes count for NestingMax, NestingGroup, NestingGroupSecurity, NestingGroupDistribution. It allows for easy filtering where we expect security groups only when there are nested distribution groups.
 
    .PARAMETER All
    Adds details about groups, and their nesting. Without this parameter only unique users and computers are returned
 
    .EXAMPLE
    Get-WinADGroupMember -Identity 'EVOTECPL\Domain Admins' -All
 
    .EXAMPLE
    Get-WinADGroupMember -Group 'GDS-TestGroup9' -All -SelfOnly | Format-List *
 
    .EXAMPLE
    Get-WinADGroupMember -Group 'GDS-TestGroup9' | Format-Table *
 
    .EXAMPLE
    Get-WinADGroupMember -Group 'GDS-TestGroup9' -All -AddSelf | Format-Table *
 
    .EXAMPLE
    Get-WinADGroupMember -Group 'GDS-TestGroup9' -All -AddSelf -AdditionalStatistics | Format-Table *
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param([alias('GroupName', 'Group')][Parameter(ValuefromPipeline, Mandatory)][Array] $Identity,
        [switch] $AddSelf,
        [switch] $All,
        [switch] $ClearCache,
        [switch] $AdditionalStatistics,
        [switch] $SelfOnly,
        [Parameter(DontShow)][int] $Nesting = -1,
        [Parameter(DontShow)][System.Collections.Generic.List[object]] $CollectedGroups,
        [Parameter(DontShow)][System.Object] $Circular,
        [Parameter(DontShow)][System.Collections.IDictionary] $InitialGroup,
        [Parameter(DontShow)][switch] $Nested)
    Begin {
        $Properties = 'GroupName', 'Name', 'SamAccountName', 'DisplayName', 'Enabled', 'Type', 'Nesting', 'CrossForest', 'ParentGroup', 'ParentGroupDomain', 'GroupDomainName', 'DistinguishedName', 'Sid'
        if (-not $Script:WinADGroupMemberCache -or $ClearCache) {
            $Script:WinADGroupMemberCache = @{}
            $Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
            $Script:WinADForestCache = @{Forest = $Forest
                Domains                         = $Forest.Domains.Name
            }
        }
        if ($Nesting -eq -1) { $MembersCache = [ordered] @{} }
    }
    Process {
        [Array] $Output = foreach ($GroupName in $Identity) {
            if (-not $Nested.IsPresent) {
                $InitialGroup = [ordered] @{GroupName = $GroupName
                    Name                              = $null
                    SamAccountName                    = $null
                    DomainName                        = $null
                    DisplayName                       = $null
                    Enabled                           = $null
                    GroupType                         = $null
                    GroupScope                        = $null
                    Type                              = 'group'
                    DirectMembers                     = 0
                    DirectGroups                      = 0
                    IndirectMembers                   = 0
                    TotalMembers                      = 0
                    Nesting                           = $Nesting
                    CircularDirect                    = $false
                    CircularIndirect                  = $false
                    CrossForest                       = $false
                    ParentGroup                       = ''
                    ParentGroupDomain                 = ''
                    GroupDomainName                   = $null
                    DistinguishedName                 = $null
                    Sid                               = $null
                }
                $CollectedGroups = [System.Collections.Generic.List[string]]::new()
                $Nesting = -1
            }
            $Nesting++
            $ADGroupName = Get-WinADObject -Identity $GroupName -IncludeGroupMembership
            if ($ADGroupName) {
                if (-not $Nested.IsPresent) {
                    $InitialGroup.GroupName = $ADGroupName.Name
                    $InitialGroup.DomainName = $ADGroupName.DomainName
                    if ($AddSelf -or $SelfOnly) {
                        $InitialGroup.Name = $ADGroupName.Name
                        $InitialGroup.SamAccountName = $ADGroupName.SamAccountName
                        $InitialGroup.DisplayName = $ADGroupName.DisplayName
                        $InitialGroup.GroupDomainName = $ADGroupName.DomainName
                        $InitialGroup.DistinguishedName = $ADGroupName.DistinguishedName
                        $InitialGroup.Sid = $ADGroupName.ObjectSID
                        $InitialGroup.GroupType = $ADGroupName.GroupType
                        $InitialGroup.GroupScope = $ADGroupName.GroupScope
                    }
                }
                $Script:WinADGroupMemberCache[$ADGroupName.DistinguishedName] = $ADGroupName
                if ($Circular -or $CollectedGroups -contains $ADGroupName.DistinguishedName) {
                    [Array] $NestedMembers = foreach ($MyIdentity in $ADGroupName.Members) {
                        if ($Script:WinADGroupMemberCache[$MyIdentity]) { $Script:WinADGroupMemberCache[$MyIdentity] } else {
                            $ADObject = Get-WinADObject -Identity $MyIdentity -IncludeGroupMembership
                            $Script:WinADGroupMemberCache[$MyIdentity] = $ADObject
                            $Script:WinADGroupMemberCache[$MyIdentity]
                        }
                    }
                    [Array] $NestedMembers = foreach ($Member in $NestedMembers) { if ($CollectedGroups -notcontains $Member.DistinguishedName) { $Member } }
                    $Circular = $null
                } else {
                    [Array] $NestedMembers = foreach ($MyIdentity in $ADGroupName.Members) {
                        if ($Script:WinADGroupMemberCache[$MyIdentity]) { $Script:WinADGroupMemberCache[$MyIdentity] } else {
                            $ADObject = Get-WinADObject -Identity $MyIdentity -IncludeGroupMembership
                            $Script:WinADGroupMemberCache[$MyIdentity] = $ADObject
                            $Script:WinADGroupMemberCache[$MyIdentity]
                        }
                    }
                }
                if (-not $MembersCache[$ADGroupName.DistinguishedName]) {
                    $DirectMembers = $NestedMembers.Where( { $_.ObjectClass -ne 'group' }, 'split')
                    $MembersCache[$ADGroupName.DistinguishedName] = [ordered] @{DirectMembers = ($DirectMembers[0])
                        DirectMembersCount                                                    = ($DirectMembers[0]).Count
                        DirectGroups                                                          = ($DirectMembers[1])
                        DirectGroupsCount                                                     = ($DirectMembers[1]).Count
                        IndirectMembers                                                       = [System.Collections.Generic.List[PSCustomObject]]::new()
                        IndirectMembersCount                                                  = $null
                        IndirectGroups                                                        = [System.Collections.Generic.List[PSCustomObject]]::new()
                        IndirectGroupsCount                                                   = $null
                    }
                }
                foreach ($NestedMember in $NestedMembers) {
                    $DomainParentGroup = ConvertFrom-DistinguishedName -DistinguishedName $ADGroupName.DistinguishedName -ToDomainCN
                    $CreatedObject = [ordered] @{GroupName = $InitialGroup.GroupName
                        Name                               = $NestedMember.name
                        SamAccountName                     = $NestedMember.SamAccountName
                        DomainName                         = $NestedMember.DomainName
                        DisplayName                        = $NestedMember.DisplayName
                        Enabled                            = $NestedMember.Enabled
                        GroupType                          = $NestedMember.GroupType
                        GroupScope                         = $NestedMember.GroupScope
                        Type                               = $NestedMember.ObjectClass
                        DirectMembers                      = 0
                        DirectGroups                       = 0
                        IndirectMembers                    = 0
                        TotalMembers                       = 0
                        Nesting                            = $Nesting
                        CircularDirect                     = $false
                        CircularIndirect                   = $false
                        CrossForest                        = $false
                        ParentGroup                        = $ADGroupName.name
                        ParentGroupDomain                  = $DomainParentGroup
                        GroupDomainName                    = $InitialGroup.DomainName
                        DistinguishedName                  = $NestedMember.DistinguishedName
                        Sid                                = $NestedMember.ObjectSID
                    }
                    if ($NestedMember.DomainName -notin $Script:WinADForestCache['Domains']) { $CreatedObject['CrossForest'] = $true }
                    if ($NestedMember.ObjectClass -eq "group") {
                        if ($ADGroupName.memberof -contains $NestedMember.DistinguishedName) {
                            $Circular = $ADGroupName.DistinguishedName
                            $CreatedObject['CircularDirect'] = $true
                        }
                        $CollectedGroups.Add($ADGroupName.DistinguishedName)
                        if ($CollectedGroups -contains $NestedMember.DistinguishedName) { $CreatedObject['CircularIndirect'] = $true }
                        if ($All) { [PSCustomObject] $CreatedObject }
                        Write-Verbose "Get-WinADGroupMember - Going into $($NestedMember.DistinguishedName) (Nesting: $Nesting) (Circular:$Circular)"
                        $OutputFromGroup = Get-WinADGroupMember -GroupName $NestedMember -Nesting $Nesting -Circular $Circular -InitialGroup $InitialGroup -CollectedGroups $CollectedGroups -Nested -All:$All.IsPresent
                        $OutputFromGroup
                        foreach ($Member in $OutputFromGroup) { if ($Member.Type -eq 'group') { $MembersCache[$ADGroupName.DistinguishedName]['IndirectGroups'].Add($Member) } else { $MembersCache[$ADGroupName.DistinguishedName]['IndirectMembers'].Add($Member) } }
                    } else { [PSCustomObject] $CreatedObject }
                }
            }
        }
    }
    End {
        if ($Output.Count -gt 0) {
            if ($Nesting -eq 0) {
                if (-not $All) { $Output | Sort-Object -Unique -Property DistinguishedName | Select-Object -Property $Properties } else {
                    if ($AddSelf -or $SelfOnly) {
                        $InitialGroup.DirectMembers = $MembersCache[$InitialGroup.DistinguishedName].DirectMembersCount
                        $InitialGroup.DirectGroups = $MembersCache[$InitialGroup.DistinguishedName].DirectGroupsCount
                        foreach ($Group in $MembersCache[$InitialGroup.DistinguishedName].DirectGroups) { $InitialGroup.IndirectMembers = $MembersCache[$Group.DistinguishedName].DirectMembersCount + $InitialGroup.IndirectMembers }
                        $AllMembersForGivenGroup = @(foreach ($DirectGroup in $MembersCache[$InitialGroup.DistinguishedName].DirectGroups) { $MembersCache[$DirectGroup.DistinguishedName].DirectMembers }
                            $MembersCache[$InitialGroup.DistinguishedName].DirectMembers
                            $MembersCache[$InitialGroup.DistinguishedName].IndirectMembers)
                        $InitialGroup['TotalMembers'] = @($AllMembersForGivenGroup | Sort-Object -Unique -Property DistinguishedName).Count
                        if ($AdditionalStatistics -or $SelfOnly) {
                            $InitialGroup['NestingMax'] = ($Output.Nesting | Sort-Object -Unique -Descending)[0]
                            $NestingObjectTypes = $Output.Where( { $_.Type -eq 'group' }, 'split')
                            $NestingGroupTypes = $NestingObjectTypes[0].Where( { $_.GroupType -eq 'Security' }, 'split')
                            $InitialGroup['NestingGroup'] = ($NestingObjectTypes[0]).Count
                            $InitialGroup['NestingGroupSecurity'] = ($NestingGroupTypes[0]).Count
                            $InitialGroup['NestingGroupDistribution'] = ($NestingGroupTypes[1]).Count
                        }
                        [PSCustomObject] $InitialGroup
                    }
                    if (-not $SelfOnly) {
                        foreach ($Object in $Output) {
                            if ($Object.Type -eq 'group') {
                                $Object.DirectMembers = $MembersCache[$Object.DistinguishedName].DirectMembersCount
                                $Object.DirectGroups = $MembersCache[$Object.DistinguishedName].DirectGroupsCount
                                foreach ($DirectGroup in $MembersCache[$Object.DistinguishedName].DirectGroups) { $Object.IndirectMembers = $MembersCache[$DirectGroup.DistinguishedName].DirectMembersCount + $Object.IndirectMembers }
                                $AllMembersForGivenGroup = @(foreach ($DirectGroup in $MembersCache[$Object.DistinguishedName].DirectGroups) { $MembersCache[$DirectGroup.DistinguishedName].DirectMembers }
                                    $MembersCache[$Object.DistinguishedName].DirectMembers
                                    $MembersCache[$Object.DistinguishedName].IndirectMembers)
                                $Object.TotalMembers = @($AllMembersForGivenGroup | Sort-Object -Unique -Property DistinguishedName).Count
                                $Object
                            } else { $Object }
                        }
                    }
                }
            } else { $Output }
        }
    }
}
function Get-WinADGroupMemberOf {
    [cmdletBinding()]
    param([parameter(Position = 0, Mandatory)][Array] $Identity,
        [switch] $AddSelf,
        [switch] $ClearCache,
        [Parameter(DontShow)][int] $Nesting = -1,
        [Parameter(DontShow)][System.Collections.Generic.List[object]] $CollectedGroups,
        [Parameter(DontShow)][System.Object] $Circular,
        [Parameter(DontShow)][System.Collections.IDictionary] $InitialObject,
        [Parameter(DontShow)][switch] $Nested)
    Begin { if (-not $Script:WinADGroupObjectCache -or $ClearCache) { $Script:WinADGroupObjectCache = @{} } }
    Process {
        [Array] $Output = foreach ($MyObject in $Identity) {
            $Object = Get-WinADObject -Identity $MyObject
            Write-Verbose "Get-WinADGroupMemberOf - starting $($Object.Name)/$($Object.DomainName)"
            if (-not $Nested.IsPresent) {
                $InitialObject = [ordered] @{ObjectName = $Object.Name
                    ObjectSamAccountName                = $Object.SamAccountName
                    Name                                = $Object.Name
                    SamAccountName                      = $Object.SamAccountName
                    DomainName                          = $Object.DomainName
                    DisplayName                         = $Object.DisplayName
                    Enabled                             = $Object.Enabled
                    Type                                = $Object.ObjectClass
                    GroupType                           = $Object.GroupType
                    GroupScope                          = $Object.GroupScope
                    Nesting                             = $Nesting
                    Circular                            = $false
                    ParentGroup                         = ''
                    ParentGroupDomain                   = ''
                    ObjectDomainName                    = $Object.DomainName
                    DistinguishedName                   = $Object.Distinguishedname
                    Sid                                 = $Object.ObjectSID
                }
                $CollectedGroups = [System.Collections.Generic.List[string]]::new()
                $Nesting = -1
            }
            $Nesting++
            if ($Object) {
                $Script:WinADGroupObjectCache[$Object.DistinguishedName] = $Object
                if ($Circular) {
                    [Array] $NestedMembers = foreach ($MyIdentity in $Object.MemberOf) {
                        if ($Script:WinADGroupObjectCache[$MyIdentity]) { $Script:WinADGroupObjectCache[$MyIdentity] } else {
                            Write-Verbose "Get-WinADGroupMemberOf - Requesting more data on $MyIdentity (Circular: $true)"
                            $ADObject = Get-WinADObject -Identity $MyIdentity
                            $Script:WinADGroupObjectCache[$MyIdentity] = $ADObject
                            $Script:WinADGroupObjectCache[$MyIdentity]
                        }
                    }
                    [Array] $NestedMembers = foreach ($Member in $NestedMembers) { if ($CollectedGroups -notcontains $Member.DistinguishedName) { $Member } }
                    $Circular = $null
                } else {
                    [Array] $NestedMembers = foreach ($MyIdentity in $Object.MemberOf) {
                        if ($Script:WinADGroupObjectCache[$MyIdentity]) { $Script:WinADGroupObjectCache[$MyIdentity] } else {
                            Write-Verbose "Get-WinADGroupMemberOf - Requesting more data on $MyIdentity (Circular: $false)"
                            $ADObject = Get-WinADObject -Identity $MyIdentity
                            $Script:WinADGroupObjectCache[$MyIdentity] = $ADObject
                            $Script:WinADGroupObjectCache[$MyIdentity]
                        }
                    }
                }
                foreach ($NestedMember in $NestedMembers) {
                    Write-Verbose "Get-WinADGroupMemberOf - processing $($InitialObject.ObjectName) nested member $($NestedMember.name)"
                    $CreatedObject = [ordered] @{ObjectName = $InitialObject.ObjectName
                        ObjectSamAccountName                = $InitialObject.SamAccountName
                        Name                                = $NestedMember.name
                        SamAccountName                      = $NestedMember.SamAccountName
                        DomainName                          = $NestedMember.DomainName
                        DisplayName                         = $NestedMember.DisplayName
                        Enabled                             = $NestedMember.Enabled
                        Type                                = $NestedMember.ObjectClass
                        GroupType                           = $NestedMember.GroupType
                        GroupScope                          = $NestedMember.GroupScope
                        Nesting                             = $Nesting
                        Circular                            = $false
                        ParentGroup                         = $Object.name
                        ParentGroupDomain                   = $Object.DomainName
                        ObjectDomainName                    = $InitialObject.DomainName
                        DistinguishedName                   = $NestedMember.DistinguishedName
                        Sid                                 = $NestedMember.ObjectSID
                    }
                    if ($NestedMember.ObjectClass -eq "group") {
                        if ($Object.members -contains $NestedMember.DistinguishedName) {
                            $Circular = $Object.DistinguishedName
                            $CreatedObject['Circular'] = $true
                        }
                        $CollectedGroups.Add($Object.DistinguishedName)
                        [PSCustomObject] $CreatedObject
                        Write-Verbose "Get-WinADGroupMemberOf - Going deeper with $($NestedMember.name)"
                        $OutputFromGroup = Get-WinADGroupMemberOf -Identity $NestedMember -Nesting $Nesting -Circular $Circular -InitialObject $InitialObject -CollectedGroups $CollectedGroups -Nested
                        $OutputFromGroup
                    } else { [PSCustomObject] $CreatedObject }
                }
            }
        }
    }
    End {
        if ($Output.Count -gt 0) {
            if ($Nesting -eq 0) {
                if ($AddSelf) { [PSCustomObject] $InitialObject }
                foreach ($MyObject in $Output) { $MyObject }
            } else { $Output }
        }
    }
}
function Get-WinADLastBackup {
    <#
    .SYNOPSIS
    Gets Active directory forest or domain last backup time
 
    .DESCRIPTION
    Gets Active directory forest or domain last backup time
 
    .PARAMETER Domain
    Optionally you can pass Domains by hand
 
    .EXAMPLE
    $LastBackup = Get-WinADLastBackup
    $LastBackup | Format-Table -AutoSize
 
    .EXAMPLE
    $LastBackup = Get-WinADLastBackup -Domain 'ad.evotec.pl'
    $LastBackup | Format-Table -AutoSize
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $NameUsed = [System.Collections.Generic.List[string]]::new()
    [DateTime] $CurrentDate = Get-Date
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        try {
            [string[]]$Partitions = (Get-ADRootDSE -Server $QueryServer -ErrorAction Stop).namingContexts
            [System.DirectoryServices.ActiveDirectory.DirectoryContextType] $contextType = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain
            [System.DirectoryServices.ActiveDirectory.DirectoryContext] $context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext($contextType, $Domain)
            [System.DirectoryServices.ActiveDirectory.DomainController] $domainController = [System.DirectoryServices.ActiveDirectory.DomainController]::FindOne($context)
        } catch { Write-Warning "Get-WinADLastBackup - Failed to gather partitions information for $Domain with error $($_.Exception.Message)" }
        $Output = ForEach ($Name in $Partitions) {
            if ($NameUsed -contains $Name) { continue } else { $NameUsed.Add($Name) }
            $domainControllerMetadata = $domainController.GetReplicationMetadata($Name)
            $dsaSignature = $domainControllerMetadata.Item("dsaSignature")
            try { $LastBackup = [DateTime] $($dsaSignature.LastOriginatingChangeTime) } catch { $LastBackup = [DateTime]::MinValue }
            [PSCustomObject] @{Domain = $Domain
                NamingContext         = $Name
                LastBackup            = $LastBackup
                LastBackupDaysAgo     = - (Convert-TimeToDays -StartTime ($CurrentDate) -EndTime ($LastBackup))
            }
        }
        $Output
    }
}
function Get-WinADLDAPBindingsSummary {
    [cmdletbinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [int] $Days = 1,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    $Events = Get-Events -LogName 'Directory Service' -ID 2887 -Machine $ForestInformation.ForestDomainControllers.HostName -DateFrom ((Get-Date).Date.adddays(-$Days))
    foreach ($Event in $Events) {
        [PSCustomobject] @{'Domain Controller'                                         = $Event.Computer
            'Date'                                                                     = $Event.Date
            'Number of simple binds performed without SSL/TLS'                         = $Event.'NoNameA0'
            'Number of Negotiate/Kerberos/NTLM/Digest binds performed without signing' = $Event.'NoNameA1'
            'GatheredFrom'                                                             = $Event.'GatheredFrom'
            'GatheredLogName'                                                          = $Event.'GatheredLogName'
        }
    }
}
function Get-WinADLMSettings {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'DomainController')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [int] $Days = 1,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    foreach ($ComputerName in $ForestInformation.ForestDomainControllers.HostName) {
        $LSA = Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Control\Lsa' -ComputerName $ComputerName
        if ($Lsa -and $Lsa.PSError -eq $false) {
            if ($LSA.lmcompatibilitylevel) { $LMCompatibilityLevel = $LSA.lmcompatibilitylevel } else { $LMCompatibilityLevel = 3 }
            $LM = @{0 = 'Server sends LM and NTLM response and never uses extended session security. Clients use LM and NTLM authentication, and never use extended session security. DCs accept LM, NTLM, and NTLM v2 authentication.'
                1     = 'Servers use NTLM v2 session security if it is negotiated. Clients use LM and NTLM authentication and use extended session security if the server supports it. DCs accept LM, NTLM, and NTLM v2 authentication.'
                2     = 'Server sends NTLM response only. Clients use only NTLM authentication and use extended session security if the server supports it. DCs accept LM, NTLM, and NTLM v2 authentication.'
                3     = 'Server sends NTLM v2 response only. Clients use NTLM v2 authentication and use extended session security if the server supports it. DCs accept LM, NTLM, and NTLM v2 authentication.'
                4     = 'DCs refuse LM responses. Clients use NTLM authentication and use extended session security if the server supports it. DCs refuse LM authentication but accept NTLM and NTLM v2 authentication.'
                5     = 'DCs refuse LM and NTLM responses, and accept only NTLM v2. Clients use NTLM v2 authentication and use extended session security if the server supports it. DCs refuse NTLM and LM authentication, and accept only NTLM v2 authentication.'
            }
            [PSCustomObject] @{ComputerName = $ComputerName
                LSAProtectionCredentials    = [bool] $LSA.RunAsPPL
                Level                       = $LMCompatibilityLevel
                LevelDescription            = $LM[$LMCompatibilityLevel]
                EveryoneIncludesAnonymous   = [bool] $LSA.everyoneincludesanonymous
                LimitBlankPasswordUse       = [bool] $LSA.LimitBlankPasswordUse
                NoLmHash                    = [bool] $LSA.NoLmHash
                DisableDomainCreds          = [bool] $LSA.disabledomaincreds
                ForceGuest                  = [bool] $LSA.forceguest
                RestrictAnonymous           = [bool] $LSA.restrictanonymous
                RestrictAnonymousSAM        = [bool] $LSA.restrictanonymoussam
                SecureBoot                  = [bool] $LSA.SecureBoot
                LsaCfgFlagsDefault          = $LSA.LsaCfgFlagsDefault
                LSAPid                      = $LSA.LSAPid
                AuditBaseDirectories        = [bool] $LSA.auditbasedirectories
                AuditBaseObjects            = [bool] $LSA.auditbaseobjects
                CrashOnAuditFail            = $LSA.CrashOnAuditFail
            }
        } else {
            [PSCustomObject] @{ComputerName = $ComputerName
                LSAProtectionCredentials    = $null
                Level                       = $null
                LevelDescription            = $null
                EveryoneIncludesAnonymous   = $null
                LimitBlankPasswordUse       = $null
                NoLmHash                    = $null
                DisableDomainCreds          = $null
                ForceGuest                  = $null
                RestrictAnonymous           = $null
                RestrictAnonymousSAM        = $null
                SecureBoot                  = $null
                LsaCfgFlagsDefault          = $null
                LSAPid                      = $null
                AuditBaseDirectories        = $null
                AuditBaseObjects            = $null
                CrashOnAuditFail            = $null
            }
        }
    }
}
function Get-WinADObject {
    <#
    .SYNOPSIS
    Gets Active Directory Object
 
    .DESCRIPTION
    Returns Active Directory Object (Computers, Groups, Users or ForeignSecurityPrincipal) using ADSI
 
    .PARAMETER Identity
    Identity of an object. It can be SamAccountName, SID, DistinguishedName or multiple other options
 
    .PARAMETER DomainName
    Choose domain name the objects resides in. This is optional for most objects
 
    .PARAMETER Credential
    Parameter description
 
    .PARAMETER IncludeGroupMembership
    Queries for group members when object is a group
 
    .PARAMETER IncludeAllTypes
    Allows functions to return all objects types and not only Computers, Groups, Users or ForeignSecurityPrincipal
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param([Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][Array] $Identity,
        [string] $DomainName,
        [pscredential] $Credential,
        [switch] $IncludeGroupMembership,
        [switch] $IncludeAllTypes,
        [switch] $AddType,
        [switch] $Cache,
        [string[]] $Properties)
    Begin {
        if ($Cache -and -not $Script:CacheObjectsWinADObject) { $Script:CacheObjectsWinADObject = @{} }
        Add-Type -AssemblyName System.DirectoryServices.AccountManagement
        $GroupTypes = @{'2' = @{Name = 'Distribution Group - Global'
                Type                 = 'Distribution'
                Scope                = 'Global'
            }
            '4'             = @{Name = 'Distribution Group - Domain Local'
                Type                 = 'Distribution'
                Scope                = 'Domain local'
            }
            '8'             = @{Name = 'Distribution Group - Universal'
                Type                 = 'Distribution'
                Scope                = 'Universal'
            }
            '-2147483640'   = @{Name = 'Security Group - Universal'
                Type                 = 'Security'
                Scope                = 'Universal'
            }
            '-2147483643'   = @{Name = 'Security Group - Builtin Local'
                Type                 = 'Security'
                Scope                = 'Builtin local'
            }
            '-2147483644'   = @{Name = 'Security Group - Domain Local'
                Type                 = 'Security'
                Scope                = 'Domain local'
            }
            '-2147483646'   = @{Name = 'Security Group - Global'
                Type                 = 'Security'
                Scope                = 'Global'
            }
        }
    }
    process {
        foreach ($Ident in $Identity) {
            $ResolvedIdentity = $null
            if ($Ident.DistinguishedName) { $Ident = $Ident.DistinguishedName }
            $TemporaryName = $Ident
            $TemporaryDomainName = $DomainName
            if ($Cache -and $Script:CacheObjectsWinADObject[$TemporaryName]) {
                Write-Verbose "Get-WinADObject - Requesting $TemporaryName from Cache"
                $Script:CacheObjectsWinADObject[$TemporaryName]
                continue
            }
            if (-not $TemporaryDomainName) {
                $MatchRegex = [Regex]::Matches($Ident, "S-\d-\d+-(\d+-|){1,14}\d+")
                if ($MatchRegex.Success) {
                    $ResolvedIdentity = ConvertFrom-SID -SID $MatchRegex.Value
                    $TemporaryDomainName = $ResolvedIdentity.DomainName
                    $Ident = $MatchRegex.Value
                } elseif ($Ident -like '*\*') {
                    $ResolvedIdentity = Convert-Identity -Identity $Ident
                    if ($ResolvedIdentity.SID) {
                        $TemporaryDomainName = $ResolvedIdentity.DomainName
                        $Ident = $ResolvedIdentity.SID
                    } else {
                        $NetbiosConversion = ConvertFrom-NetbiosName -Identity $Ident
                        if ($NetbiosConversion.DomainName) {
                            $TemporaryDomainName = $NetbiosConversion.DomainName
                            $Ident = $NetbiosConversion.Name
                        }
                    }
                } elseif ($Ident -like '*@*') {
                    $CNConversion = $Ident -split '@', 2
                    $TemporaryDomainName = $CNConversion[1]
                    $Ident = $CNConversion[0]
                } elseif ($Ident -like '*DC=*') {
                    $DNConversion = ConvertFrom-DistinguishedName -DistinguishedName $Ident -ToDomainCN
                    $TemporaryDomainName = $DNConversion
                } elseif ($Ident -like '*.*') {
                    $ResolvedIdentity = Convert-Identity -Identity $Ident
                    if ($ResolvedIdentity.SID) {
                        $TemporaryDomainName = $ResolvedIdentity.DomainName
                        $Ident = $ResolvedIdentity.SID
                    } else {
                        $CNConversion = $Ident -split '\.', 2
                        $Ident = $CNConversion[0]
                        $TemporaryDomainName = $CNConversion[1]
                    }
                }
            }
            $Search = [System.DirectoryServices.DirectorySearcher]::new()
            if ($TemporaryDomainName) { try { $Context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain', $TemporaryDomainName) } catch { Write-Warning "Get-WinADObject - Building context failed ($TemporaryDomainName), error: $($_.Exception.Message)" } } else { try { $Context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain') } catch { Write-Warning "Get-WinADObject - Building context failed, error: $($_.Exception.Message)" } }
            Try {
                $IdentityGUID = ""
                ([System.Guid]$Ident).ToByteArray() | ForEach-Object { $IdentityGUID += $("\{0:x2}" -f $_) }
            } Catch { $IdentityGUID = "null" }
            $Search.filter = "(|(DistinguishedName=$Ident)(Name=$Ident)(SamAccountName=$Ident)(UserPrincipalName=$Ident)(objectGUID=$IdentityGUID)(objectSid=$Ident))"
            if ($TemporaryDomainName) { $Search.SearchRoot = "LDAP://$TemporaryDomainName" }
            if ($PSBoundParameters['Credential']) {
                $Cred = [System.DirectoryServices.DirectoryEntry]::new("LDAP://$TemporaryDomainName", $($Credential.UserName), $($Credential.GetNetworkCredential().password))
                $Search.SearchRoot = $Cred
            }
            Write-Verbose "Get-WinADObject - Requesting $Ident ($TemporaryDomainName)"
            try { $SearchResults = $($Search.FindAll()) } catch {
                if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "Get-WinADObject - Requesting $Ident ($TemporaryDomainName) failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" } else {
                    Write-Warning "Get-WinADObject - Requesting $Ident ($TemporaryDomainName) failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))"
                    continue
                }
            }
            if ($SearchResults.Count -lt 1) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "Requesting $Ident ($TemporaryDomainName) failed with no results." } }
            foreach ($Object in $SearchResults) {
                $UAC = Convert-UserAccountControl -UserAccountControl ($Object.properties.useraccountcontrol -as [string])
                $ObjectClass = ($Object.properties.objectclass -as [array])[-1]
                if ($ObjectClass -notin 'group', 'computer', 'user', 'foreignSecurityPrincipal' -and (-not $IncludeAllTypes)) {
                    Write-Warning "Get-WinADObject - Unsupported object ($Ident) of type $ObjectClass. Only user,computer,group and foreignSecurityPrincipal is supported."
                    continue
                }
                $Members = $Object.properties.member -as [array]
                if ($ObjectClass -eq 'group') {
                    if ($IncludeGroupMembership) {
                        $GroupMembers = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context, $Ident).Members
                        [Array] $Members = foreach ($Member in $GroupMembers) { if ($Member.DistinguishedName) { $Member.DistinguishedName } elseif ($Member.DisplayName) { $Member.DisplayName } else { $Member.Sid } }
                    }
                }
                $ObjectDomainName = ConvertFrom-DistinguishedName -DistinguishedName ($Object.properties.distinguishedname -as [string]) -ToDomainCN
                $DisplayName = $Object.properties.displayname -as [string]
                $SamAccountName = $Object.properties.samaccountname -as [string]
                $Name = $Object.properties.name -as [string]
                if ($ObjectClass -eq 'foreignSecurityPrincipal' -and $DisplayName -eq '') {
                    $DisplayName = $ResolvedIdentity.Name
                    if ($DisplayName -like '*\*') {
                        $NetbiosWithName = $DisplayName -split '\\'
                        if ($NetbiosWithName.Count -eq 2) {
                            $NetbiosUser = $NetbiosWithName[1]
                            $Name = $NetbiosUser
                            $SamAccountName = $NetbiosUser
                        } else { $Name = $DisplayName }
                    } else { $Name = $DisplayName }
                }
                $GroupType = $Object.properties.grouptype -as [string]
                if ($Object.Properties.objectsid) {
                    try { $ObjectSID = [System.Security.Principal.SecurityIdentifier]::new($Object.Properties.objectsid[0], 0).Value } catch {
                        Write-Warning "Get-WinADObject - Getting objectsid failed, error: $($_.Exception.Message)"
                        $ObjectSID = $null
                    }
                } else { $ObjectSID = $null }
                $ReturnObject = [ordered] @{DisplayName = $DisplayName
                    Name                                = $Name
                    SamAccountName                      = $SamAccountName
                    ObjectClass                         = $ObjectClass
                    Enabled                             = if ($ObjectClass -eq 'group') { $null } else { $UAC -notcontains 'ACCOUNTDISABLE' }
                    PasswordNeverExpire                 = if ($ObjectClass -eq 'group') { $null } else { $UAC -contains 'DONT_EXPIRE_PASSWORD' }
                    DomainName                          = $ObjectDomainName
                    Distinguishedname                   = $Object.properties.distinguishedname -as [string]
                    WhenCreated                         = $Object.properties.whencreated -as [string]
                    WhenChanged                         = $Object.properties.whenchanged -as [string]
                    UserPrincipalName                   = $Object.properties.userprincipalname -as [string]
                    ObjectSID                           = $ObjectSID
                    MemberOf                            = $Object.properties.memberof -as [array]
                    Members                             = $Members
                    DirectReports                       = $Object.Properties.directreports
                    GroupScopedType                     = $GroupTypes[$GroupType].Name
                    GroupScope                          = $GroupTypes[$GroupType].Scope
                    GroupType                           = $GroupTypes[$GroupType].Type
                    Description                         = $Object.properties.description -as [string]
                }
                if ($Properties -contains 'LastLogonDate') {
                    $LastLogon = [int64] $Object.properties.lastlogontimestamp[0]
                    if ($LastLogon -ne 9223372036854775807) { $ReturnObject['LastLogonDate'] = [datetime]::FromFileTimeUtc($LastLogon) } else { $ReturnObject['LastLogonDate'] = $null }
                }
                if ($Properties -contains 'PasswordLastSet') {
                    $PasswordLastSet = [int64] $Object.properties.pwdlastset[0]
                    if ($PasswordLastSet -ne 9223372036854775807) { $ReturnObject['PasswordLastSet'] = [datetime]::FromFileTimeUtc($PasswordLastSet) } else { $ReturnObject['PasswordLastSet'] = $null }
                }
                if ($Properties -contains 'AccountExpirationDate') {
                    $ExpirationDate = [int64] $Object.properties.accountexpires[0]
                    if ($ExpirationDate -ne 9223372036854775807) { $ReturnObject['AccountExpirationDate'] = [datetime]::FromFileTimeUtc($ExpirationDate) } else { $ReturnObject['AccountExpirationDate'] = $null }
                }
                if ($AddType) {
                    if (-not $ResolvedIdentity) { $ResolvedIdentity = ConvertFrom-SID -SID $ReturnObject['ObjectSID'] }
                    $ReturnObject['Type'] = $ResolvedIdentity.Type
                }
                if ($ReturnObject['Type'] -eq 'WellKnownAdministrative') { if (-not $TemporaryDomainName) { $ReturnObject['DomainName'] = '' } }
                if ($Cache) {
                    $Script:CacheObjectsWinADObject[$TemporaryName] = [PSCustomObject] $ReturnObject
                    $Script:CacheObjectsWinADObject[$TemporaryName]
                } else { [PSCustomObject] $ReturnObject }
            }
        }
    }
}
Function Get-WinADPrivilegedObjects {
    [alias('Get-WinADPriviligedObjects')]
    [cmdletbinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [switch] $LegitimateOnly,
        [switch] $OrphanedOnly,
        [switch] $SummaryOnly,
        [switch] $DoNotShowCriticalSystemObjects,
        [alias('Display')][switch] $Formatted,
        [string] $Splitter = [System.Environment]::NewLine,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $Domains = $ForestInformation.Domains
    $UsersWithAdminCount = foreach ($Domain in $Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        if ($DoNotShowCriticalSystemObjects) { $Objects = Get-ADObject -Filter 'admincount -eq 1 -and iscriticalsystemobject -notlike "*"' -Server $QueryServer -Properties whenchanged, whencreated, admincount, isCriticalSystemObject, samaccountname, "msDS-ReplAttributeMetaData" } else { $Objects = Get-ADObject -Filter 'admincount -eq 1' -Server $QueryServer -Properties whenchanged, whencreated, admincount, isCriticalSystemObject, samaccountname, "msDS-ReplAttributeMetaData" }
        foreach ($_ in $Objects) {
            [PSCustomObject] @{Domain  = $Domain
                distinguishedname      = $_.distinguishedname
                whenchanged            = $_.whenchanged
                whencreated            = $_.whencreated
                admincount             = $_.admincount
                SamAccountName         = $_.SamAccountName
                objectclass            = $_.objectclass
                isCriticalSystemObject = if ($_.isCriticalSystemObject) { $true } else { $false }
                adminCountDate         = ($_.'msDS-ReplAttributeMetaData' | ForEach-Object { ([XML]$_.Replace("`0", "")).DS_REPL_ATTR_META_DATA | Where-Object { $_.pszAttributeName -eq "admincount" } }).ftimeLastOriginatingChange | Get-Date -Format MM/dd/yyyy
            }
        }
    }
    $CriticalGroups = foreach ($Domain in $Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        Get-ADGroup -Filter 'admincount -eq 1 -and iscriticalsystemobject -eq $true' -Server $QueryServer
    }
    $CacheCritical = @{}
    foreach ($Group in $CriticalGroups) {
        $Members = Get-WinADGroupMember -Identity $Group.distinguishedname -Verbose:$false
        foreach ($Member in $Members) {
            if (-not $CacheCritical[$Member.DistinguishedName]) { $CacheCritical[$Member.DistinguishedName] = [System.Collections.Generic.List[string]]::new() }
            $CacheCritical[$Member.DistinguishedName].Add($Group.DistinguishedName)
        }
    }
    $AdminCountAll = foreach ($object in $UsersWithAdminCount) {
        $DistinguishedName = $object.distinguishedname
        [Array] $IsMemberGroups = foreach ($Group in $CriticalGroups) { $CacheCritical[$DistinguishedName] -contains $Group.DistinguishedName }
        $IsMember = $IsMemberGroups -contains $true
        $GroupDomains = $CacheCritical[$DistinguishedName]
        $IsOrphaned = -not $Object.isCriticalSystemObject -and -not $IsMember
        if ($Formatted) {
            $GroupDomains = $GroupDomains -join $Splitter
            $User = [PSCustomObject] @{DistinguishedName = $Object.DistinguishedName
                Domain                                   = $Object.domain
                IsOrphaned                               = $IsOrphaned
                IsMember                                 = $IsMember
                IsCriticalSystemObject                   = $Object.isCriticalSystemObject
                Admincount                               = $Object.admincount
                AdminCountDate                           = $Object.adminCountDate
                WhenCreated                              = $Object.whencreated
                ObjectClass                              = $Object.objectclass
                GroupDomain                              = $GroupDomains
            }
        } else {
            $User = [PSCustomObject] @{'DistinguishedName' = $Object.DistinguishedName
                'Domain'                                   = $Object.domain
                'IsOrphaned'                               = $IsOrphaned
                'IsMember'                                 = $IsMember
                'IsCriticalSystemObject'                   = $Object.isCriticalSystemObject
                'AdminCount'                               = $Object.admincount
                'AdminCountDate'                           = $Object.adminCountDate
                'WhenCreated'                              = $Object.whencreated
                'ObjectClass'                              = $Object.objectclass
                'GroupDomain'                              = $GroupDomains
            }
        }
        $User
    }
    $Output = @(if ($OrphanedOnly) { $AdminCountAll | Where-Object { $_.IsOrphaned } } elseif ($LegitimateOnly) { $AdminCountAll | Where-Object { $_.IsOrphaned -eq $false } } else { $AdminCountAll })
    if ($SummaryOnly) { $Output | Group-Object ObjectClass | Select-Object -Property Name, Count } else { $Output }
}
function Get-WinADProxyAddresses {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER ADUser
    ADUser Object
 
    .PARAMETER RemovePrefix
    Removes prefix from proxy address such as SMTP: or smtp:
 
    .PARAMETER ToLower
    Makes sure all returned data is lower case
 
    .PARAMETER Formatted
    Makes sure data is formatted for display, rather than for working with objects
 
    .PARAMETER Splitter
    Splitter or Joiner that connects data together such as an array of 3 aliases
 
    .EXAMPLE
    $ADUsers = Get-ADUser -Filter * -Properties ProxyAddresses
    foreach ($User in $ADUsers) {
        Get-WinADProxyAddresses -ADUser $User
    }
 
    .EXAMPLE
    $ADUsers = Get-ADUser -Filter * -Properties ProxyAddresses
    foreach ($User in $ADUsers) {
        Get-WinADProxyAddresses -ADUser $User -RemovePrefix
    }
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([Object] $ADUser,
        [switch] $RemovePrefix,
        [switch] $ToLower,
        [switch] $Formatted,
        [alias('Joiner')][string] $Splitter = ',')
    $Summary = [PSCustomObject] @{EmailAddress = $ADUser.EmailAddress
        Primary                                = [System.Collections.Generic.List[string]]::new()
        Secondary                              = [System.Collections.Generic.List[string]]::new()
        Sip                                    = [System.Collections.Generic.List[string]]::new()
        x500                                   = [System.Collections.Generic.List[string]]::new()
        Other                                  = [System.Collections.Generic.List[string]]::new()
        Broken                                 = [System.Collections.Generic.List[string]]::new()
    }
    foreach ($Proxy in $ADUser.ProxyAddresses) {
        if ($Proxy -like '*,*') { $Summary.Broken.Add($Proxy) } elseif ($Proxy.StartsWith('SMTP:')) {
            if ($RemovePrefix) { $Proxy = $Proxy -replace 'SMTP:', '' }
            if ($ToLower) { $Proxy = $Proxy.ToLower() }
            $Summary.Primary.Add($Proxy)
        } elseif ($Proxy.StartsWith('smtp:') -or $Proxy -notlike "*:*") {
            if ($RemovePrefix) { $Proxy = $Proxy -replace 'smtp:', '' }
            if ($ToLower) { $Proxy = $Proxy.ToLower() }
            $Summary.Secondary.Add($Proxy)
        } elseif ($Proxy.StartsWith('x500')) {
            if ($RemovePrefix) { $Proxy = $Proxy }
            if ($ToLower) { $Proxy = $Proxy.ToLower() }
            $Summary.x500.Add($Proxy)
        } elseif ($Proxy.StartsWith('sip:')) {
            if ($RemovePrefix) { $Proxy = $Proxy }
            if ($ToLower) { $Proxy = $Proxy.ToLower() }
            $Summary.Sip.Add($Proxy)
        } else {
            if ($RemovePrefix) { $Proxy = $Proxy }
            if ($ToLower) { $Proxy = $Proxy.ToLower() }
            $Summary.Other.Add($Proxy)
        }
    }
    if ($Formatted) {
        $Summary.Primary = $Summary.Primary -join $Splitter
        $Summary.Secondary = $Summary.Secondary -join $Splitter
        $Summary.Sip = $Summary.Sip -join $Splitter
        $Summary.x500 = $Summary.x500 -join $Splitter
        $Summary.Other = $Summary.Other -join $Splitter
    }
    $Summary
}
function Get-WinADServiceAccount {
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [switch] $PerDomain)
    $Today = Get-Date
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $Output = [ordered] @{}
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $Properties = @('Name', 'ObjectClass', 'PasswordLastSet', 'PasswordNeverExpires', 'PasswordNotRequired', 'UserPrincipalName', 'SamAccountName', 'LastLogonDate'
            'AccountExpirationDate', 'AccountNotDelegated',
            'CanonicalName', 'WhenCreated', 'WhenChanged', 'DistinguishedName', 'Enabled', 'Description'
            'msDS-HostServiceAccountBL', 'msDS-SupportedEncryptionTypes', 'msDS-User-Account-Control-Computed', 'TrustedForDelegation', 'TrustedToAuthForDelegation'
            'msDS-AuthenticatedAtDC', 'msDS-AllowedToActOnBehalfOfOtherIdentity', 'msDS-AllowedToDelegateTo', 'PrincipalsAllowedToRetrieveManagedPassword', 'PrincipalsAllowedToDelegateToAccount'
            'msDS-ManagedPasswordInterval', 'msDS-GroupMSAMembership', 'ManagedPasswordIntervalInDays', 'msDS-RevealedDSAs', 'servicePrincipalName')
        $Accounts = Get-ADServiceAccount -Filter * -Server $QueryServer -Properties $Properties
        $Output[$Domain] = foreach ($Account in $Accounts) {
            if ($null -ne $Account.LastLogonDate) { [int] $LastLogonDays = "$(-$($Account.LastLogonDate - $Today).Days)" } else { $LastLogonDays = $null }
            if ($null -ne $Account.PasswordLastSet) { [int] $PasswordLastChangedDays = "$(-$($Account.PasswordLastSet - $Today).Days)" } else { $PasswordLastChangedDays = $null }
            [PSCUstomObject] @{Name                          = $Account.Name
                Enabled                                      = $Account.Enabled
                ObjectClass                                  = $Account.ObjectClass
                CanonicalName                                = $Account.CanonicalName
                DomainName                                   = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Account.DistinguishedName
                Description                                  = $Account.Description
                PasswordLastChangedDays                      = $PasswordLastChangedDays
                LastLogonDays                                = $LastLogonDays
                'ManagedPasswordIntervalInDays'              = $Account.'ManagedPasswordIntervalInDays'
                'msDS-AllowedToDelegateTo'                   = $Account.'msDS-AllowedToDelegateTo'
                'msDS-HostServiceAccountBL'                  = $Account.'msDS-HostServiceAccountBL'
                'msDS-AuthenticatedAtDC'                     = $Account.'msDS-AuthenticatedAtDC'
                'msDS-AllowedToActOnBehalfOfOtherIdentity'   = $Account.'msDS-AllowedToActOnBehalfOfOtherIdentity'
                'PrincipalsAllowedToRetrieveManagedPassword' = $Account.'PrincipalsAllowedToRetrieveManagedPassword'
                'PrincipalsAllowedToDelegateToAccount'       = $Account.'PrincipalsAllowedToDelegateToAccount'
                'msDS-GroupMSAMembershipAccess'              = $Account.'msDS-GroupMSAMembership'.Access.IdentityReference.Value
                'msDS-GroupMSAMembershipOwner'               = $Account.'msDS-GroupMSAMembership'.Owner
                'msDS-RevealedDSAs'                          = $Account.'msDS-RevealedDSAs'
                'servicePrincipalName'                       = $Account.servicePrincipalName
                AccountNotDelegated                          = $Account.AccountNotDelegated
                TrustedForDelegation                         = $Account.TrustedForDelegation
                TrustedToAuthForDelegation                   = $Account.TrustedToAuthForDelegation
                AccountExpirationDate                        = $Account.AccountExpirationDate
                msDSSupportedEncryptionTypes                 = Get-ADEncryptionTypes -Value $Account.'msds-supportedencryptiontypes'
                PasswordNeverExpires                         = $Account.PasswordNeverExpires
                PasswordNotRequired                          = $Account.PasswordNotRequired
                LastLogonDate                                = $Account.LastLogonDate
                PasswordLastSet                              = $Account.PasswordLastSet
                WhenChanged                                  = $Account.WhenChanged
                WhenCreated                                  = $Account.WhenCreated
                SamAccountName                               = $Account.SamAccountName
                DistinguishedName                            = $Account.DistinguishedName
                'msDS-GroupMSAMembership'                    = $Account.'msDS-GroupMSAMembership'
            }
        }
    }
    if ($PerDomain) { $Output } else { $Output.Values }
}
function Get-WinADSharePermission {
    [cmdletBinding(DefaultParameterSetName = 'Path')]
    param([Parameter(ParameterSetName = 'Path', Mandatory)][string] $Path,
        [Parameter(ParameterSetName = 'ShareType', Mandatory)][validateset('NetLogon', 'SYSVOL')][string[]] $ShareType,
        [switch] $Owner,
        [string[]] $Name,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    if ($ShareType) {
        $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        foreach ($Domain in $ForestInformation.Domains) {
            $Path = -join ("\\", $Domain, "\$ShareType")
            @(Get-Item -Path $Path -Force) + @(Get-ChildItem -Path $Path -Recurse:$true -Force -ErrorAction SilentlyContinue -ErrorVariable Err) | ForEach-Object -Process { if ($Owner) {
                    $Output = Get-FileOwner -JustPath -Path $_ -Resolve -AsHashTable
                    $Output['Attributes'] = $_.Attributes
                    [PSCustomObject] $Output
                } else {
                    $Output = Get-FilePermission -Path $_ -ResolveTypes -Extended -AsHashTable
                    foreach ($O in $Output) {
                        $O['Attributes'] = $_.Attributes
                        [PSCustomObject] $O
                    }
                } }
        }
    } else {
        if ($Path -and (Test-Path -Path $Path)) {
            @(Get-Item -Path $Path -Force) + @(Get-ChildItem -Path $Path -Recurse:$true -Force -ErrorAction SilentlyContinue -ErrorVariable Err) | ForEach-Object -Process { if ($Owner) {
                    $Output = Get-FileOwner -JustPath -Path $_ -Resolve -AsHashTable -Verbose
                    $Output['Attributes'] = $_.Attributes
                    [PSCustomObject] $Output
                } else {
                    $Output = Get-FilePermission -Path $_ -ResolveTypes -Extended -AsHashTable
                    foreach ($O in $Output) {
                        $O['Attributes'] = $_.Attributes
                        [PSCustomObject] $O
                    }
                } }
        }
    }
    foreach ($e in $err) { Write-Warning "Get-WinADSharePermission - $($e.Exception.Message) ($($e.CategoryInfo.Reason))" }
}
function Get-WinADSiteConnections {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [alias('Joiner')][string] $Splitter,
        [switch] $Formatted,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    [Flags()]
    enum ConnectionOption {
        None
        IsGenerated
        TwoWaySync
        OverrideNotifyDefault = 4
        UseNotify = 8
        DisableIntersiteCompression = 16
        UserOwnedSchedule = 32
        RodcTopology = 64
    }
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    $QueryServer = $ForestInformation['QueryServers'][$($ForestInformation.Forest.Name)]['HostName'][0]
    $NamingContext = (Get-ADRootDSE -Server $QueryServer).configurationNamingContext
    $Connections = Get-ADObject â€“SearchBase $NamingContext -LDAPFilter "(objectCategory=ntDSConnection)" -Properties * -Server $QueryServer
    $FormmatedConnections = foreach ($_ in $Connections) {
        if ($null -eq $_.Options) { $Options = 'None' } else { $Options = ([ConnectionOption] $_.Options) -split ', ' }
        if ($Formatted) {
            $Dictionary = [PSCustomObject] @{'CN' = $_.CN
                'Description'                     = $_.Description
                'Display Name'                    = $_.DisplayName
                'Enabled Connection'              = $_.enabledConnection
                'Server From'                     = if ($_.fromServer -match '(?<=CN=NTDS Settings,CN=)(.*)(?=,CN=Servers,)') { $Matches[0] } else { $_.fromServer }
                'Server To'                       = if ($_.DistinguishedName -match '(?<=CN=NTDS Settings,CN=)(.*)(?=,CN=Servers,)') { $Matches[0] } else { $_.fromServer }
                'Site From'                       = if ($_.fromServer -match '(?<=,CN=Servers,CN=)(.*)(?=,CN=Sites,CN=Configuration)') { $Matches[0] } else { $_.fromServer }
                'Site To'                         = if ($_.DistinguishedName -match '(?<=,CN=Servers,CN=)(.*)(?=,CN=Sites,CN=Configuration)') { $Matches[0] } else { $_.fromServer }
                'Options'                         = if ($Splitter -ne '') { $Options -Join $Splitter } else { $Options }
                'When Created'                    = $_.WhenCreated
                'When Changed'                    = $_.WhenChanged
                'Is Deleted'                      = $_.IsDeleted
            }
        } else {
            $Dictionary = [PSCustomObject] @{CN = $_.CN
                Description                     = $_.Description
                DisplayName                     = $_.DisplayName
                EnabledConnection               = $_.enabledConnection
                ServerFrom                      = if ($_.fromServer -match '(?<=CN=NTDS Settings,CN=)(.*)(?=,CN=Servers,)') { $Matches[0] } else { $_.fromServer }
                ServerTo                        = if ($_.DistinguishedName -match '(?<=CN=NTDS Settings,CN=)(.*)(?=,CN=Servers,)') { $Matches[0] } else { $_.fromServer }
                SiteFrom                        = if ($_.fromServer -match '(?<=,CN=Servers,CN=)(.*)(?=,CN=Sites,CN=Configuration)') { $Matches[0] } else { $_.fromServer }
                SiteTo                          = if ($_.DistinguishedName -match '(?<=,CN=Servers,CN=)(.*)(?=,CN=Sites,CN=Configuration)') { $Matches[0] } else { $_.fromServer }
                Options                         = if ($Splitter -ne '') { $Options -Join $Splitter } else { $Options }
                WhenCreated                     = $_.WhenCreated
                WhenChanged                     = $_.WhenChanged
                IsDeleted                       = $_.IsDeleted
            }
        }
        $Dictionary
    }
    $FormmatedConnections
}
function Get-WinADSiteLinks {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [alias('Joiner')][string] $Splitter,
        [string] $Formatted,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    [Flags()]
    enum SiteLinksOptions {
        None = 0
        UseNotify = 1
        TwoWaySync = 2
        DisableCompression = 4
    }
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0]
    $NamingContext = (Get-ADRootDSE -Server $QueryServer).configurationNamingContext
    $SiteLinks = Get-ADObject -LDAPFilter "(objectCategory=sitelink)" â€“SearchBase $NamingContext -Properties * -Server $QueryServer
    foreach ($_ in $SiteLinks) {
        if ($null -eq $_.Options) { $Options = 'None' } else { $Options = ([SiteLinksOptions] $_.Options) -split ', ' }
        if ($Formatted) {
            [PSCustomObject] @{Name                  = $_.CN
                Cost                                 = $_.Cost
                'Replication Frequency In Minutes'   = $_.ReplInterval
                Options                              = if ($Splitter -ne '') { $Options -Join $Splitter } else { $Options }
                Created                              = $_.WhenCreated
                Modified                             = $_.WhenChanged
                'Protected From Accidental Deletion' = $_.ProtectedFromAccidentalDeletion
            }
        } else {
            [PSCustomObject] @{Name             = $_.CN
                Cost                            = $_.Cost
                ReplicationFrequencyInMinutes   = $_.ReplInterval
                Options                         = if ($Splitter -ne '') { $Options -Join $Splitter } else { $Options }
                Created                         = $_.WhenCreated
                Modified                        = $_.WhenChanged
                ProtectedFromAccidentalDeletion = $_.ProtectedFromAccidentalDeletion
            }
        }
    }
}
function Get-WinADTomebstoneLifetime {
    [Alias('Get-WinADForestTomebstoneLifetime')]
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0]
    $RootDSE = Get-ADRootDSE -Server $QueryServer
    $Output = (Get-ADObject -Server $QueryServer -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$(($RootDSE).configurationNamingContext)" -Properties tombstoneLifetime)
    if ($null -eq $Output) { [PSCustomObject] @{TombstoneLifeTime = 60 } } else { [PSCustomObject] @{TombstoneLifeTime = $Output.tombstoneLifetime } }
}
function Get-WinADTrust {
    [alias('Get-WinADTrusts')]
    [cmdletBinding()]
    param([string] $Forest,
        [switch] $Recursive,
        [Parameter(DontShow)][int] $Nesting = -1,
        [Parameter(DontShow)][System.Collections.IDictionary] $UniqueTrusts)
    Begin { if ($Nesting -eq -1) { $UniqueTrusts = [ordered]@{} } }
    Process {
        $Nesting++
        $ForestInformation = Get-WinADForest -Forest $Forest
        [Array] $Trusts = @(try {
                $TrustRelationship = $ForestInformation.GetAllTrustRelationships()
                foreach ($Trust in $TrustRelationship) {
                    [ordered] @{Type  = 'Forest'
                        Details       = $Trust
                        ExecuteObject = $ForestInformation
                    }
                }
            } catch { Write-Warning "Get-WinADForest - Can't process trusts for $Forest, error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" }
            foreach ($Domain in $ForestInformation.Domains) {
                $DomainInformation = Get-WinADDomain -Domain $Domain.Name
                try {
                    $TrustRelationship = $DomainInformation.GetAllTrustRelationships()
                    foreach ($Trust in $TrustRelationship) {
                        [ordered] @{Type  = 'Domain'
                            Details       = $Trust
                            ExecuteObject = $DomainInformation
                        }
                    }
                } catch { Write-Warning "Get-WinADForest - Can't process trusts for $Domain, error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" }
            })
        [Array] $Output = foreach ($Trust in $Trusts) {
            Write-Verbose "Get-WinADTrust - From: $($Trust.Details.SourceName) To: $($Trust.Details.TargetName) Nesting: $Nesting"
            $UniqueID1 = -join ($Trust.Details.SourceName, $Trust.Details.TargetName)
            $UniqueID2 = -join ($Trust.Details.TargetName, $Trust.Details.SourceName)
            if (-not $UniqueTrusts[$UniqueID1]) { $UniqueTrusts[$UniqueID1] = $true } else {
                Write-Verbose "Get-WinADTrust - Trust already on the list (From: $($Trust.Details.SourceName) To: $($Trust.Details.TargetName) Nesting: $Nesting)"
                continue
            }
            if (-not $UniqueTrusts[$UniqueID2]) { $UniqueTrusts[$UniqueID2] = $true } else {
                Write-Verbose "Get-WinADTrust - Trust already on the list (Reverse) (From: $($Trust.Details.TargetName) To: $($Trust.Details.SourceName) Nesting: $Nesting"
                continue
            }
            $TrustObject = Get-WinADTrustObject -Domain $Trust.ExecuteObject.Name -AsHashTable
            if ($TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Enable TGT DELEGATION") { $TGTDelegation = $true } elseif ($TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "No TGT DELEGATION") { $TGTDelegation = $false } else { $TGTDelegation = $false }
            $TrustStatus = Test-DomainTrust -Domain $Trust.Details.SourceName -TrustedDomain $Trust.Details.TargetName
            $GroupExists = Get-WinADObject -Identity 'S-1-5-32-544' -DomainName $Trust.Details.TargetName
            [PsCustomObject] @{'TrustSource' = $Trust.Details.SourceName
                'TrustTarget'                = $Trust.Details.TargetName
                'TrustDirection'             = $Trust.Details.TrustDirection.ToString()
                'TrustBase'                  = $Trust.Type
                'TrustType'                  = $Trust.Details.TrustType.ToString()
                'TrustTypeAD'                = $TrustObject[$Trust.Details.TargetName].TrustType
                'Level'                      = $Nesting
                'SuffixesIncluded'           = (($Trust.Details.TopLevelNames | Where-Object { $_.Status -eq 'Enabled' }).Name) -join ', '
                'SuffixesExcluded'           = $Trust.Details.ExcludedTopLevelNames.Name
                'TrustAttributes'            = $TrustObject[$Trust.Details.TargetName].TrustAttributes -join ', '
                'TrustStatus'                = $TrustStatus.TrustStatus
                'QueryStatus'                = if ($GroupExists) { 'OK' } else { 'NOT OK' }
                'ForestTransitive'           = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Forest Transitive"
                'SelectiveAuthentication'    = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Cross Organization"
                'SIDFilteringQuarantined'    = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Quarantined Domain"
                'DisallowTransitivity'       = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Non Transitive"
                'IntraForest'                = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Within Forest"
                'IsTGTDelegationEnabled'     = $TGTDelegation
                'UplevelOnly'                = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "UpLevel Only"
                'UsesAESKeys'                = $TrustObject[$Trust.Details.TargetName].msDSSupportedEncryptionTypes -contains "AES128-CTS-HMAC-SHA1-96" -or $TrustObject[$Trust.Details.TargetName].msDSSupportedEncryptionTypes -contains 'AES256-CTS-HMAC-SHA1-96'
                'UsesRC4Encryption'          = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Uses RC4 Encryption"
                'EncryptionTypes'            = $TrustObject[$Trust.Details.TargetName].msDSSupportedEncryptionTypes -join ', '
                'TrustSourceDC'              = $TrustStatus.TrustSourceDC
                'TrustTargetDC'              = $TrustStatus.TrustTargetDC
                'ObjectGUID'                 = $TrustObject[$Trust.Details.TargetName].ObjectGuid
                'ObjectSID'                  = $TrustObject[$Trust.Details.TargetName].ObjectSID
                'Created'                    = $TrustObject[$Trust.Details.TargetName].WhenCreated
                'Modified'                   = $TrustObject[$Trust.Details.TargetName].WhenChanged
                'TrustDirectionText'         = $TrustObject[$Trust.Details.TargetName].TrustDirectionText
                'TrustTypeText'              = $TrustObject[$Trust.Details.TargetName].TrustTypeText
                'AdditionalInformation'      = [ordered] @{'msDSSupportedEncryptionTypes' = $TrustObject[$Trust.Details.TargetName].msDSSupportedEncryptionTypes
                    'msDSTrustForestTrustInfo'                                            = $TrustObject[$Trust.Details.TargetName].msDSTrustForestTrustInfo
                    'SuffixesInclude'                                                     = $Trust.Details.TopLevelNames
                    'SuffixesExclude'                                                     = $Trust.Details.ExcludedTopLevelNames
                    'TrustObject'                                                         = $TrustObject
                    'GroupExists'                                                         = $GroupExists
                }
            }
        }
        if ($Output -and $Output.Count -gt 0) { $Output }
        if ($Recursive) { foreach ($Trust in $Output) { if ($Trust.TrustType -notin 'TreeRoot', 'ParentChild') { Get-WinADTrust -Forest $Trust.TrustTarget -Recursive -Nesting $Nesting -UniqueTrusts $UniqueTrusts } } }
    }
}
function Get-WinADTrustLegacy {
    [CmdletBinding()]
    param([string] $Forest,
        [alias('Domain')][string[]] $IncludeDomains,
        [string[]] $ExcludeDomains,
        [switch] $Display,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [switch] $Unique)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $UniqueTrusts = [ordered]@{}
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $Trusts = Get-ADTrust -Server $QueryServer -Filter * -Properties *
        $DomainPDC = $ForestInformation['DomainDomainControllers'][$Domain] | Where-Object { $_.IsPDC -eq $true }
        $PropertiesTrustWMI = @('FlatName',
            'SID',
            'TrustAttributes',
            'TrustDirection',
            'TrustedDCName',
            'TrustedDomain',
            'TrustIsOk',
            'TrustStatus',
            'TrustStatusString',
            'TrustType')
        $TrustStatatuses = Get-CimInstance -ClassName Microsoft_DomainTrustStatus -Namespace root\MicrosoftActiveDirectory -ComputerName $DomainPDC.HostName -ErrorAction SilentlyContinue -Verbose:$false -Property $PropertiesTrustWMI
        $ReturnData = foreach ($Trust in $Trusts) {
            if ($Unique) {
                $UniqueID1 = -join ($Domain, $Trust.trustPartner)
                $UniqueID2 = -join ($Trust.trustPartner, $Domain)
                if (-not $UniqueTrusts[$UniqueID1]) { $UniqueTrusts[$UniqueID1] = $true } else { continue }
                if (-not $UniqueTrusts[$UniqueID2]) { $UniqueTrusts[$UniqueID2] = $true } else { continue }
            }
            $TrustWMI = $TrustStatatuses | & { process { if ($_.TrustedDomain -eq $Trust.Target) { $_ } } }
            if ($Display) {
                [PsCustomObject] @{'Trust Source' = $Domain
                    'Trust Target'                = $Trust.Target
                    'Trust Direction'             = $Trust.Direction.ToString()
                    'Trust Attributes'            = if ($Trust.TrustAttributes -is [int]) { (Get-ADTrustAttributes -Value $Trust.TrustAttributes) -join '; ' } 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
                    'Is Tree Parent'              = $Trust.IsTreeParent
                    'Is Tree Root'                = $Trust.IsTreeRoot
                    'TGTDelegation'               = $Trust.TGTDelegation
                    'TrustedPolicy'               = $Trust.TrustedPolicy
                    'TrustingPolicy'              = $Trust.TrustingPolicy
                    'TrustType'                   = $Trust.TrustType.ToString()
                    '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 }
                }
            } else {
                [PsCustomObject] @{'TrustSource' = $Domain
                    'TrustTarget'                = $Trust.Target
                    'TrustDirection'             = $Trust.Direction.ToString()
                    'TrustAttributes'            = if ($Trust.TrustAttributes -is [int]) { Get-ADTrustAttributes -Value $Trust.TrustAttributes } else { 'Error - needs fixing' }
                    'TrustStatus'                = if ($null -ne $TrustWMI) { $TrustWMI.TrustStatusString } else { 'N/A' }
                    'ForestTransitive'           = $Trust.ForestTransitive
                    'SelectiveAuthentication'    = $Trust.SelectiveAuthentication
                    'SIDFiltering Forest Aware'  = $Trust.SIDFilteringForestAware
                    'SIDFiltering Quarantined'   = $Trust.SIDFilteringQuarantined
                    'DisallowTransivity'         = $Trust.DisallowTransivity
                    'IntraForest'                = $Trust.IntraForest
                    'IsTreeParent'               = $Trust.IsTreeParent
                    'IsTreeRoot'                 = $Trust.IsTreeRoot
                    'TGTDelegation'              = $Trust.TGTDelegation
                    'TrustedPolicy'              = $Trust.TrustedPolicy
                    'TrustingPolicy'             = $Trust.TrustingPolicy
                    'TrustType'                  = $Trust.TrustType.ToString()
                    'UplevelOnly'                = $Trust.UplevelOnly
                    'UsesAESKeys'                = $Trust.UsesAESKeys
                    'UsesRC4Encryption'          = $Trust.UsesRC4Encryption
                    'TrustSourceDC'              = if ($null -ne $TrustWMI) { $TrustWMI.PSComputerName } else { '' }
                    'TrustTargetDC'              = if ($null -ne $TrustWMI) { $TrustWMI.TrustedDCName.Replace('\\', '') } else { '' }
                    'TrustSourceDN'              = $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 }
                    'TrustStatusInt'             = if ($null -ne $TrustWMI) { $TrustWMI.TrustStatus } else { -1 }
                }
            }
        }
        $ReturnData
    }
}
function Get-WinADUserPrincipalName {
    [cmdletbinding()]
    param([Parameter(Mandatory = $true)][Object] $User,
        [Parameter(Mandatory = $true)][string] $DomainName,
        [switch] $ReplaceDomain,
        [switch] $NameSurname,
        [switch] $FixLatinChars,
        [switch] $ToLower)
    if ($User.UserPrincipalName) {
        $NewUserName = $User.UserPrincipalName
        if ($ReplaceDomain) {
            $NewUserName = ($User.UserPrincipalName -split '@')[0]
            $NewUserName = -join ($NewUserName, '@', $DomainName)
        }
        if ($NameSurname) { if ($User.GivenName -and $User.Surname) { $NewUsername = -join ($User.GivenName, '.', $User.Surname, '@', $DomainName) } else { Write-Warning "Get-WinADUserPrincipalName - UserPrincipalName couldn't be changed to GivenName.SurName@$DomainName" } }
        if ($FixLatinChars) { $NewUsername = Remove-StringLatinCharacters -String $NewUsername }
        if ($ToLower) { $NewUsername = $NewUserName.ToLower() }
        if ($NewUserName -eq $User.UserPrincipalName) { Write-Warning "Get-WinADUserPrincipalName - UserPrincipalName didn't change. Stays as $NewUserName" }
        $NewUsername
    }
}
function Get-WinADUsers {
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [switch] $PerDomain)
    if (-not $Script:Cache) {
        $Script:Cache = [ordered] @{}
        $Script:AllUsers = [ordered] @{}
    }
    $Script:AllContacts = [ordered] @{}
    $Today = Get-Date
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $Properties = @('DistinguishedName', 'mail', 'LastLogonDate', 'PasswordLastSet', 'DisplayName', 'Manager', 'Description',
            'PasswordNeverExpires', 'PasswordNotRequired', 'PasswordExpired', 'UserPrincipalName', 'SamAccountName', 'CannotChangePassword',
            'TrustedForDelegation', 'TrustedToAuthForDelegation', 'msExchMailboxGuid', 'msExchRemoteRecipientType', 'msExchRecipientTypeDetails',
            'msExchRecipientDisplayType', 'pwdLastSet', "msDS-UserPasswordExpiryTimeComputed",
            'WhenCreated', 'WhenChanged')
        $AllUsers[$Domain] = Get-ADUser -Filter * -Server $QueryServer -Properties $Properties
        foreach ($Domain In $ForestInformation.Domains) { $AllUsers[$Domain] = Get-ADUser -Filter * -Properties $Properties -Server $ForestInformation['QueryServers'][$Domain].HostName[0] }
        foreach ($Domain In $ForestInformation.Domains) { $AllContacts[$Domain] = Get-ADObject -Filter 'objectClass -eq "contact"' -Properties SamAccountName, Mail, Name, DistinguishedName, WhenChanged, Whencreated, DisplayName }
    }
    if (-not $Script:Cache -or $Script:Cache.Count -eq 0) {
        $Script:Cache = @{}
        foreach ($Domain in $AllUsers.Keys) { foreach ($U in $AllUsers[$Domain]) { $Script:Cache[$U.DistinguishedName] = $U } }
        foreach ($Domain in $AllContacts.Keys) { foreach ($C in $AllContacts[$Domain]) { $Script:Cache[$C.DistinguishedName] = $C } }
    }
    $Output = [ordered] @{}
    foreach ($Domain in $ForestInformation.Domains) {
        if (-not $Script:Cache) {
            $Script:Cache = @{}
            foreach ($Domain in $AllUsers.Keys) { foreach ($U in $AllUsers[$Domain]) { $Script:Cache[$U.DistinguishedName] = $U } }
            foreach ($Domain in $AllComputers.Keys) { foreach ($C in $AllComputers[$Domain]) { $Script:Cache[$C.DistinguishedName] = $C } }
        }
        $Output[$Domain] = foreach ($User in $AllUsers[$Domain]) {
            $UserLocation = ($User.DistinguishedName -split ',').Replace('OU=', '').Replace('CN=', '').Replace('DC=', '')
            $Region = $UserLocation[-4]
            $Country = $UserLocation[-5]
            if ($User.LastLogonDate) { $LastLogonDays = $( - $($User.LastLogonDate - $Today).Days) } else { $LastLogonDays = $null }
            if ($User.PasswordLastSet) { $PasswordLastDays = $( - $($User.PasswordLastSet - $Today).Days) } else { $PasswordLastDays = $null }
            if ($User.Manager) {
                $Manager = $Cache[$User.Manager].DisplayName
                $ManagerSamAccountName = $Cache[$User.Manager].SamAccountName
                $ManagerEmail = $Cache[$User.Manager].Mail
                $ManagerEnabled = $Cache[$User.Manager].Enabled
                $ManagerLastLogon = $Cache[$User.Manager].LastLogonDate
                if ($ManagerLastLogon) { $ManagerLastLogonDays = $( - $($ManagerLastLogon - $Today).Days) } else { $ManagerLastLogonDays = $null }
                $ManagerStatus = if ($ManagerEnabled -eq $true) { 'Enabled' } elseif ($ManagerEnabled -eq $false) { 'Disabled' } else { 'Not available' }
            } else {
                if ($User.ObjectClass -eq 'user') { $ManagerStatus = 'Missing' } else { $ManagerStatus = 'Not available' }
                $Manager = $null
                $ManagerSamAccountName = $null
                $ManagerEmail = $null
                $ManagerEnabled = $null
                $ManagerLastLogon = $null
                $ManagerLastLogonDays = $null
            }
            if ($User."msDS-UserPasswordExpiryTimeComputed" -ne 9223372036854775807) {
                try { $DateExpiry = ([datetime]::FromFileTime($User."msDS-UserPasswordExpiryTimeComputed")) } catch { $DateExpiry = $User."msDS-UserPasswordExpiryTimeComputed" }
                try { $DaysToExpire = (New-TimeSpan -Start (Get-Date) -End ([datetime]::FromFileTime($User."msDS-UserPasswordExpiryTimeComputed"))).Days } catch { $DaysToExpire = $null }
                $PasswordNeverExpires = $User.PasswordNeverExpires
            } else { $PasswordNeverExpires = $true }
            if ($PasswordNeverExpires -or $null -eq $User.PasswordLastSet) {
                $DateExpiry = $null
                $DaysToExpire = $null
            }
            if ($User.'msExchMailboxGuid') { $HasMailbox = $true } else { $HasMailbox = $false }
            $msExchRecipientTypeDetails = Convert-ExchangeRecipient -msExchRecipientTypeDetails $User.msExchRecipientTypeDetails
            $msExchRecipientDisplayType = Convert-ExchangeRecipient -msExchRecipientDisplayType $User.msExchRecipientDisplayType
            $msExchRemoteRecipientType = Convert-ExchangeRecipient -msExchRemoteRecipientType $User.msExchRemoteRecipientType
            [PSCustomObject] @{Name         = $User.Name
                SamAccountName              = $User.SamAccountName
                Domain                      = $Domain
                WhenChanged                 = $User.WhenChanged
                Enabled                     = $User.Enabled
                ObjectClass                 = $User.ObjectClass
                HasMailbox                  = $HasMailbox
                MustChangePasswordAtLogon   = if ($User.pwdLastSet -eq 0 -and $User.PasswordExpired -eq $true) { $true } else { $false }
                PasswordNeverExpires        = $PasswordNeverExpires
                PasswordNotRequired         = $User.PasswordNotRequired
                LastLogonDays               = $LastLogonDays
                PasswordLastDays            = $PasswordLastDays
                DaysToExpire                = $DaysToExpire
                ManagerStatus               = $ManagerStatus
                Manager                     = $Manager
                ManagerSamAccountName       = $ManagerSamAccountName
                ManagerEmail                = $ManagerEmail
                ManagerLastLogonDays        = $ManagerLastLogonDays
                Level0                      = $Region
                Level1                      = $Country
                DistinguishedName           = $User.DistinguishedName
                LastLogonDate               = $User.LastLogonDate
                PasswordLastSet             = $User.PasswordLastSet
                PasswordExpiresOn           = $DateExpiry
                PasswordExpired             = $User.PasswordExpired
                CannotChangePassword        = $User.CannotChangePassword
                AccountTrustedForDelegation = $User.AccountTrustedForDelegation
                ManagerDN                   = $User.Manager
                ManagerLastLogon            = $ManagerLastLogon
                Group                       = $Group
                Description                 = $User.Description
                UserPrincipalName           = $User.UserPrincipalName
                RecipientTypeDetails        = $msExchRecipientTypeDetails
                RecipientDisplayType        = $msExchRecipientDisplayType
                RemoteRecipientType         = $msExchRemoteRecipientType
                WhenCreated                 = $User.WhenCreated
            }
        }
    }
    if ($PerDomain) { $Output } else { $Output.Values }
}
function Get-WinADUsersForeignSecurityPrincipalList {
    [alias('Get-WinADUsersFP')]
    param([alias('ForestName')][string] $Forest,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [string[]] $ExcludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $ForeignSecurityPrincipalList = Get-ADObject -Filter { ObjectClass -eq 'ForeignSecurityPrincipal' } -Properties * -Server $QueryServer
        foreach ($FSP in $ForeignSecurityPrincipalList) {
            Try { $Translated = (([System.Security.Principal.SecurityIdentifier]::new($FSP.objectSid)).Translate([System.Security.Principal.NTAccount])).Value } Catch { $Translated = $null }
            Add-Member -InputObject $FSP -Name 'TranslatedName' -Value $Translated -MemberType NoteProperty -Force
        }
        $ForeignSecurityPrincipalList
    }
}
function Get-WinADWellKnownFolders {
    [cmdletBinding()]
    param([string] $Forest,
        [alias('Domain')][string[]] $IncludeDomains,
        [string[]] $ExcludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [switch] $AsCustomObject)
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $DomainInformation = Get-ADDomain -Server $Domain
        $WellKnownFolders = $DomainInformation | Select-Object -Property UsersContainer, ComputersContainer, DomainControllersContainer, DeletedObjectsContainer, SystemsContainer, LostAndFoundContainer, QuotasContainer, ForeignSecurityPrincipalsContainer
        $CurrentWellKnownFolders = [ordered] @{}
        foreach ($_ in $WellKnownFolders.PSObject.Properties.Name) { $CurrentWellKnownFolders[$_] = $DomainInformation.$_ }
        if ($AsHashtable) { $CurrentWellKnownFolders } else { [PSCustomObject] $CurrentWellKnownFolders }
    }
}
function Invoke-ADEssentials {
    [cmdletBinding()]
    param([string] $FilePath,
        [Parameter(Position = 0)][string[]] $Type,
        [switch] $PassThru,
        [switch] $HideHTML,
        [switch] $HideSteps,
        [switch] $ShowError,
        [switch] $ShowWarning,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [switch] $Online)
    Reset-ADEssentialsStatus
    $Script:AllUsers = [ordered] @{}
    $Script:Cache = [ordered] @{}
    $Script:Reporting = [ordered] @{}
    $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Invoke-ADEssentials' -RepositoryOwner 'evotecit' -RepositoryName 'ADEssentials'
    $Script:Reporting['Settings'] = @{ShowError = $ShowError.IsPresent
        ShowWarning                             = $ShowWarning.IsPresent
        HideSteps                               = $HideSteps.IsPresent
    }
    Write-Color '[i]', "[ADEssentials] ", 'Version', ' [Informative] ', $Script:Reporting['Version'] -Color Yellow, DarkGray, Yellow, DarkGray, Magenta
    $Supported = [System.Collections.Generic.List[string]]::new()
    [Array] $NotSupported = foreach ($T in $Type) { if ($T -notin $Script:ADEssentialsConfiguration.Keys) { $T } else { $Supported.Add($T) } }
    if ($Supported) { Write-Color '[i]', "[ADEssentials] ", 'Supported types', ' [Informative] ', "Chosen by user: ", ($Supported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta }
    if ($NotSupported) {
        Write-Color '[i]', "[ADEssentials] ", 'Not supported types', ' [Informative] ', "Following types are not supported: ", ($NotSupported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
        Write-Color '[i]', "[ADEssentials] ", 'Not supported types', ' [Informative] ', "Please use one/multiple from the list: ", ($Script:ADEssentialsConfiguration.Keys -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
        return
    }
    $DisplayForest = if ($Forest) { $Forest } else { 'Not defined. Using current one' }
    $DisplayIncludedDomains = if ($IncludeDomains) { $IncludeDomains -join "," } else { 'Not defined. Using all domains of forest' }
    $DisplayExcludedDomains = if ($ExcludeDomains) { $ExcludeDomains -join ',' } else { 'No exclusions provided' }
    Write-Color '[i]', "[ADEssentials] ", 'Domain Information', ' [Informative] ', "Forest: ", $DisplayForest -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
    Write-Color '[i]', "[ADEssentials] ", 'Domain Information', ' [Informative] ', "Included Domains: ", $DisplayIncludedDomains -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
    Write-Color '[i]', "[ADEssentials] ", 'Domain Information', ' [Informative] ', "Excluded Domains: ", $DisplayExcludedDomains -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
    if ($Type) {
        foreach ($T in $Script:ADEssentialsConfiguration.Keys) { $Script:ADEssentialsConfiguration[$T].Enabled = $false }
        foreach ($T in $Type) { $Script:ADEssentialsConfiguration[$T].Enabled = $true }
    }
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $Properties = @('DistinguishedName', 'mail', 'LastLogonDate', 'PasswordLastSet', 'DisplayName', 'Manager', 'Description',
            'PasswordNeverExpires', 'PasswordNotRequired', 'PasswordExpired', 'UserPrincipalName', 'SamAccountName', 'CannotChangePassword',
            'TrustedForDelegation', 'TrustedToAuthForDelegation', 'msExchMailboxGuid', 'msExchRemoteRecipientType', 'msExchRecipientTypeDetails',
            'msExchRecipientDisplayType', 'pwdLastSet', "msDS-UserPasswordExpiryTimeComputed",
            'WhenCreated', 'WhenChanged')
        $AllUsers[$Domain] = Get-ADUser -Filter * -Server $QueryServer -Properties $Properties
    }
    if (-not $Script:Cache) {
        $Script:Cache = @{}
        foreach ($Domain in $AllUsers.Keys) { foreach ($U in $AllUsers[$Domain]) { $Script:Cache[$U.DistinguishedName] = $U } }
    }
    foreach ($T in $Script:ADEssentialsConfiguration.Keys) {
        if ($Script:ADEssentialsConfiguration[$T].Enabled -eq $true) {
            $Script:Reporting[$T] = [ordered] @{Name = $Script:ADEssentialsConfiguration[$T].Name
                ActionRequired                       = $null
                Data                                 = $null
                Exclusions                           = $null
                WarningsAndErrors                    = $null
                Time                                 = $null
                Summary                              = $null
                Variables                            = Copy-Dictionary -Dictionary $Script:ADEssentialsConfiguration[$T]['Variables']
            }
            if ($Exclusions) {
                if ($Exclusions -is [scriptblock]) { $Script:Reporting[$T]['ExclusionsCode'] = $Exclusions }
                if ($Exclusions -is [Array]) { $Script:Reporting[$T]['Exclusions'] = $Exclusions }
            }
            $TimeLogADEssentials = Start-TimeLog
            Write-Color -Text '[i]', '[Start] ', $($Script:ADEssentialsConfiguration[$T]['Name']) -Color Yellow, DarkGray, Yellow
            $OutputCommand = Invoke-Command -ScriptBlock $Script:ADEssentialsConfiguration[$T]['Execute'] -WarningVariable CommandWarnings -ErrorVariable CommandErrors -ArgumentList $Forest, $ExcludeDomains, $IncludeDomains
            if ($OutputCommand -is [System.Collections.IDictionary]) { $Script:Reporting[$T]['Data'] = $OutputCommand } else { $Script:Reporting[$T]['Data'] = [Array] $OutputCommand }
            Invoke-Command -ScriptBlock $Script:ADEssentialsConfiguration[$T]['Processing']
            $Script:Reporting[$T]['WarningsAndErrors'] = @(if ($ShowWarning) {
                    foreach ($War in $CommandWarnings) {
                        [PSCustomObject] @{Type = 'Warning'
                            Comment             = $War
                            Reason              = ''
                            TargetName          = ''
                        }
                    }
                }
                if ($ShowError) {
                    foreach ($Err in $CommandErrors) {
                        [PSCustomObject] @{Type = 'Error'
                            Comment             = $Err
                            Reason              = $Err.CategoryInfo.Reason
                            TargetName          = $Err.CategoryInfo.TargetName
                        }
                    }
                })
            $TimeEndADEssentials = Stop-TimeLog -Time $TimeLogADEssentials -Option OneLiner
            $Script:Reporting[$T]['Time'] = $TimeEndADEssentials
            Write-Color -Text '[i]', '[End ] ', $($Script:ADEssentialsConfiguration[$T]['Name']), " [Time to execute: $TimeEndADEssentials]" -Color Yellow, DarkGray, Yellow, DarkGray
        }
    }
    New-HTML -Author 'PrzemysÅ‚aw KÅ‚ys' -TitleText 'ADEssentials Report' { New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey
        New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
        New-HTMLPanelStyle -BorderRadius 0px
        New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin
        New-HTMLHeader { New-HTMLSection -Invisible { New-HTMLSection { New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue } -JustifyContent flex-start -Invisible
                New-HTMLSection { New-HTMLText -Text "ADEssentials - $($Script:Reporting['Version'])" -Color Blue } -JustifyContent flex-end -Invisible } }
        if ($Type.Count -eq 1) {
            foreach ($T in $Script:ADEssentialsConfiguration.Keys) {
                if ($Script:ADEssentialsConfiguration[$T].Enabled -eq $true) {
                    if ($Script:ADEssentialsConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ADEssentialsConfiguration[$T]['Summary'] }
                    & $Script:ADEssentialsConfiguration[$T]['Solution']
                }
            }
        } else {
            foreach ($T in $Script:ADEssentialsConfiguration.Keys) {
                if ($Script:ADEssentialsConfiguration[$T].Enabled -eq $true) {
                    if ($Script:ADEssentialsConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ADEssentialsConfiguration[$T]['Summary'] }
                    New-HTMLTab -Name $Script:ADEssentialsConfiguration[$T]['Name'] { & $Script:ADEssentialsConfiguration[$T]['Solution'] }
                }
            }
        } } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath
    Reset-ADEssentialsStatus
}
[scriptblock] $SourcesAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $Script:ADEssentialsConfiguration.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" } }
Register-ArgumentCompleter -CommandName Invoke-ADEssentials -ParameterName Type -ScriptBlock $SourcesAutoCompleter
function Remove-ADACL {
    [cmdletBinding(SupportsShouldProcess)]
    param([Array] $ACL,
        [string] $Principal,
        [System.DirectoryServices.ActiveDirectoryRights] $AccessRule,
        [System.Security.AccessControl.AccessControlType] $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow)
    foreach ($SubACL in $ACL) {
        $OutputRequiresCommit = @(if ($Principal -like '*-*-*-*') { $Identity = [System.Security.Principal.SecurityIdentifier]::new($Principal) } else { [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal) }
            if (-not $AccessRule) {
                Write-Verbose "Remove-ADACL - Removing access for $($Identity) / All Rights"
                try {
                    $SubACL.ACL.RemoveAccess($Identity, $AccessControlType)
                    $true
                } catch {
                    Write-Warning "Remove-ADACL - Removing permissions for $($SubACL.DistinguishedName) failed: $($_.Exception.Message)"
                    $false
                }
            } else {
                foreach ($Rule in $AccessRule) {
                    $AccessRuleToRemove = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $Rule, $AccessControlType)
                    Write-Verbose "Remove-ADACL - Removing access for $($AccessRuleToRemove.IdentityReference) / $($AccessRuleToRemove.ActiveDirectoryRights)"
                    try {
                        $SubACL.ACL.RemoveAccessRule($AccessRuleToRemove)
                        $true
                    } catch {
                        Write-Warning "Remove-ADACL - Removing permissions for $($SubACL.DistinguishedName) failed: $($_.Exception.Message)"
                        $false
                    }
                }
            })
        if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) {
            Write-Verbose "Remove-ADACL - Saving permissions for $($SubACL.DistinguishedName)"
            try { Set-Acl -Path $SubACL.Path -AclObject $SubACL.ACL -ErrorAction Stop } catch { Write-Warning "Remove-ADACL - Saving permissions for $($SubACL.DistinguishedName) failed: $($_.Exception.Message)" }
        } elseif ($OutputRequiresCommit -contains $false) { Write-Warning "Remove-ADACL - Skipping saving permissions for $($SubACL.DistinguishedName) due to errors." }
    }
}
function Remove-WinADDuplicateObject {
    [cmdletBinding(SupportsShouldProcess)]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string] $PartialMatchDistinguishedName,
        [string[]] $IncludeObjectClass,
        [string[]] $ExcludeObjectClass,
        [int] $LimitProcessing = [int32]::MaxValue)
    $getWinADDuplicateObjectSplat = @{Forest = $Forest
        ExcludeDomains                       = $ExcludeDomains
        IncludeDomains                       = $IncludeDomains
        IncludeObjectClass                   = $IncludeObjectClass
        ExcludeObjectClass                   = $ExcludeObjectClass
        PartialMatchDistinguishedName        = $PartialMatchDistinguishedName
    }
    $Count = 0
    $DuplicateObjects = Get-WinADDuplicateObject @getWinADDuplicateObjectSplat
    foreach ($Duplicate in $DuplicateObjects | Select-Object -First $LimitProcessing) {
        $Count++
        try {
            Write-Verbose "Remove-WinADDuplicateObject - [$Count/$($DuplicateObjects.Count)] Deleting $($Duplicate.ConflictDN) / $($Duplicate.DomainName) via GUID: $($Duplicate.ObjectGUID)"
            Remove-ADObject -Identity $Duplicate.ObjectGUID -Recursive -ErrorAction Stop -Confirm:$false -Server $Duplicate.DomainName
        } catch { Write-Warning "Remove-WinADDuplicateObject - [$Count/$($DuplicateObjects.Count)] Deleting $($Duplicate.ConflictDN) / $($Duplicate.DomainName) via GUID: $($Duplicate.ObjectGUID) failed with error: $($_.Exception.Message)" }
    }
}
function Remove-WinADSharePermission {
    [cmdletBinding(DefaultParameterSetName = 'Path', SupportsShouldProcess)]
    param([Parameter(ParameterSetName = 'Path', Mandatory)][string] $Path,
        [ValidateSet('Unknown')][string] $Type = 'Unknown',
        [int] $LimitProcessing)
    Begin { [int] $Count = 0 }
    Process {
        if ($Path -and (Test-Path -Path $Path)) {
            $Data = @(Get-Item -Path $Path) + @(Get-ChildItem -Path $Path -Recurse:$true)
            foreach ($_ in $Data) {
                $PathToProcess = $_.FullName
                $Permissions = Get-FilePermission -Path $PathToProcess -Extended -IncludeACLObject -ResolveTypes
                $OutputRequiresCommit = foreach ($Permission in $Permissions) {
                    if ($Type -eq 'Unknown' -and $Permission.PrincipalType -eq 'Unknown' -and $Permission.IsInherited -eq $false) {
                        try {
                            Write-Verbose "Remove-WinADSharePermission - Removing permissions from $PathToProcess for $($Permission.Principal) / $($Permission.PrincipalType)"
                            $Permission.AllACL.RemoveAccessRule($Permission.ACL)
                            $true
                        } catch {
                            Write-Warning "Remove-WinADSharePermission - Removing permissions from $PathToProcess for $($Permission.Principal) / $($Permission.PrincipalType) failed: $($_.Exception.Message)"
                            $false
                        }
                    }
                }
                if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) {
                    try { Set-Acl -Path $PathToProcess -AclObject $Permissions[0].ALLACL -ErrorAction Stop } catch { Write-Warning "Remove-WinADSharePermission - Commit for $($PathToProcess) failed: $($_.Exception.Message)" }
                    $Count++
                    if ($Count -eq $LimitProcessing) { break }
                }
            }
        }
    }
    End {}
}
function Rename-WinADUserPrincipalName {
    [cmdletbinding()]
    param([Parameter(Mandatory = $true)][Array] $Users,
        [Parameter(Mandatory = $true)][string] $DomainName,
        [switch] $ReplaceDomain,
        [switch] $NameSurname,
        [switch] $FixLatinChars,
        [switch] $ToLower,
        [switch] $WhatIf)
    foreach ($User in $Users) {
        $NewUserPrincipalName = Get-WinADUserPrincipalName -User $User -DomainName $DomainName -ReplaceDomain:$ReplaceDomain -NameSurname:$NameSurname -FixLatinChars:$FixLatinChars -ToLower:$ToLower
        if ($NewUserPrincipalName -ne $User.UserPrincipalName) { Set-ADUser -Identity $User.DistinguishedName -UserPrincipalName $NewUserPrincipalName -WhatIf:$WhatIf }
    }
}
function Repair-WinADACLConfigurationOwner {
    <#
    .SYNOPSIS
    Fixes all owners of certain object type (site,subnet,sitelink,interSiteTransport,wellKnownSecurityPrincipal) to be Enterprise Admins
 
    .DESCRIPTION
    Fixes all owners of certain object type (site,subnet,sitelink,interSiteTransport,wellKnownSecurityPrincipal) to be Enterprise Admins
 
    .PARAMETER ObjectType
    Gets owners from one or multiple types (and only that type). Possible choices are sites, subnets, interSiteTransport, siteLink, wellKnownSecurityPrincipals
 
    .PARAMETER ContainerType
    Gets owners from one or multiple types (including containers and anything below it). Possible choices are sites, subnets, interSiteTransport, siteLink, wellKnownSecurityPrincipals, services
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER LimitProcessing
    Provide limit of objects that will be fixed in a single run
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'ObjectType', SupportsShouldProcess)]
    param([parameter(ParameterSetName = 'ObjectType', Mandatory)][ValidateSet('site', 'subnet', 'interSiteTransport', 'siteLink', 'wellKnownSecurityPrincipal')][string[]] $ObjectType,
        [parameter(ParameterSetName = 'FolderType', Mandatory)][ValidateSet('site', 'subnet', 'interSiteTransport', 'siteLink', 'wellKnownSecurityPrincipal', 'service')][string[]] $ContainerType,
        [string] $Forest,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [int] $LimitProcessing = [int32]::MaxValue)
    $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -ExtendedForestInformation $ForestInformation
    $getWinADACLConfigurationSplat = @{ContainerType = $ContainerType
        ObjectType                                   = $ObjectType
        Owner                                        = $true
        Forest                                       = $Forest
        ExtendedForestInformation                    = $ExtendedForestInformation
    }
    Remove-EmptyValue -Hashtable $getWinADACLConfigurationSplat
    Get-WinADACLConfiguration @getWinADACLConfigurationSplat | Where-Object { if ($_.OwnerType -ne 'Administrative' -and $_.OwnerType -ne 'WellKnownAdministrative') { $_ } } | Select-Object -First $LimitProcessing | ForEach-Object { $ADObject = $_
        $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $_.DistinguishedName
        $EnterpriseAdmin = $ADAdministrativeGroups[$DomainName]['EnterpriseAdmins']
        Set-ADACLOwner -ADObject $ADObject.DistinguishedName -Principal $EnterpriseAdmin }
}
function Repair-WinADEmailAddress {
    [CmdletBinding(SupportsShouldProcess)]
    param([Microsoft.ActiveDirectory.Management.ADAccount] $ADUser,
        [string] $ToEmail,
        [switch] $Display,
        [Array] $AddSecondary)
    $Summary = [ordered] @{SamAccountName = $ADUser.SamAccountName
        UserPrincipalName                 = $ADUser.UserPrincipalName
        EmailAddress                      = ''
        ProxyAddresses                    = ''
        EmailAddressStatus                = 'Not required'
        ProxyAddressesStatus              = 'Not required'
        EmailAddressError                 = ''
        ProxyAddressesError               = ''
    }
    $RequiredProperties = @('EmailAddress'
        'proxyAddresses')
    foreach ($Property in $RequiredProperties) {
        if ($ADUser.PSObject.Properties.Name -notcontains $Property) {
            Write-Warning "Repair-WinADEmailAddress - User $($ADUser.SamAccountName) is missing properties ($($RequiredProperties -join ',')) which are required. Try again."
            return
        }
    }
    $ProcessUser = Get-WinADProxyAddresses -ADUser $ADUser -RemovePrefix
    $EmailAddresses = [System.Collections.Generic.List[string]]::new()
    $ProxyAddresses = [System.Collections.Generic.List[string]]::new()
    $ExpectedUser = [ordered] @{EmailAddress = $ToEmail
        Primary                              = $ToEmail
        Secondary                            = ''
        Sip                                  = $ProcessUser.Sip
        x500                                 = $ProcessUser.x500
        Other                                = $ProcessUser.Other
    }
    if (-not $ToEmail) {
        $ExpectedUser.EmailAddress = $ProcessUser.EmailAddress
        $ExpectedUser.Primary = $ProcessUser.Primary
        if (-not $ExpectedUser.Primary -and $ExpectedUser.EmailAddress) { $ExpectedUser.Primary = $ExpectedUser.EmailAddress }
    }
    $MakePrimary = "SMTP:$($ExpectedUser.EmailAddress)"
    $ProxyAddresses.Add($MakePrimary)
    $Types = @('Sip', 'x500', 'Other')
    foreach ($Type in $Types) { foreach ($Address in $ExpectedUser.$Type) { $ProxyAddresses.Add($Address) } }
    $TypesEmails = @('Primary', 'Secondary')
    foreach ($Type in $TypesEmails) { foreach ($Address in $ProcessUser.$Type) { if ($Address -ne $ToEmail) { $EmailAddresses.Add($Address) } } }
    foreach ($Email in $EmailAddresses) { $ProxyAddresses.Add("smtp:$Email".ToLower()) }
    foreach ($Email in $AddSecondary) { if ($Email -like 'smtp:*') { $ProxyAddresses.Add($Email.ToLower()) } else { $ProxyAddresses.Add("smtp:$Email".ToLower()) } }
    $Summary['EmailAddress'] = $ExpectedUser.EmailAddress
    if ($ProcessUser.EmailAddress -ne $ExpectedUser.EmailAddress) {
        if ($PSCmdlet.ShouldProcess($ADUser, "Email $ToEmail will be set in EmailAddresss field (1)")) {
            try {
                Set-ADUser -Identity $ADUser -EmailAddress $ExpectedUser.EmailAddress -ErrorAction Stop
                $Summary['EmailAddressStatus'] = 'Success'
                $Summary['EmailAddressError'] = ''
            } catch {
                $Summary['EmailAddressStatus'] = 'Failed'
                $Summary['EmailAddressError'] = $_.Exception.Message
            }
        } else {
            $Summary['EmailAddressStatus'] = 'Whatif'
            $Summary['EmailAddressError'] = ''
        }
    }
    $UniqueProxyList = [System.Collections.Generic.List[string]]::new()
    foreach ($Proxy in $ProxyAddresses) { if ($UniqueProxyList -notcontains $Proxy) { $UniqueProxyList.Add($Proxy) } }
    [string[]] $ExpectedProxyAddresses = ($UniqueProxyList | Sort-Object | ForEach-Object { $_ })
    [string[]] $CurrentProxyAddresses = ($ADUser.ProxyAddresses | Sort-Object | ForEach-Object { $_ })
    $Summary['ProxyAddresses'] = $ExpectedProxyAddresses -join ';'
    if (Compare-Object -ReferenceObject $ExpectedProxyAddresses -DifferenceObject $CurrentProxyAddresses -CaseSensitive) {
        if ($PSCmdlet.ShouldProcess($ADUser, "Email $ExpectedProxyAddresses will replace proxy addresses (2)")) {
            try {
                Set-ADUser -Identity $ADUser -Replace @{proxyAddresses = $ExpectedProxyAddresses } -ErrorAction Stop
                $Summary['ProxyAddressesStatus'] = 'Success'
                $Summary['ProxyAddressesError'] = ''
            } catch {
                $Summary['ProxyAddressesStatus'] = 'Failed'
                $Summary['ProxyAddressesError'] = $_.Exception.Message
            }
        } else {
            $Summary['ProxyAddressesStatus'] = 'WhatIf'
            $Summary['ProxyAddressesError'] = ''
        }
    }
    if ($Display) { [PSCustomObject] $Summary }
}
function Repair-WinADForestControllerInformation {
    [cmdletBinding(SupportsShouldProcess)]
    param([parameter(Mandatory)][validateSet('Owner', 'Manager')][string[]] $Type,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [int] $LimitProcessing)
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    if (-not $ADAdministrativeGroups) { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation }
    $Fixed = 0
    $DCs = Get-WinADForestControllerInformation -Forest $Forest -ExtendedForestInformation $ForestInformation -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains | ForEach-Object { $DC = $_
        $Done = $false
        if ($Type -contains 'Owner') {
            if ($DC.OwnerType -ne 'Administrative') {
                Write-Verbose -Message "Repair-WinADForestControllerInformation - Fixing (Owner) [$($DC.DomainName)]($Count/$($DCs.Count)) $($DC.DNSHostName)"
                $Principal = $ADAdministrativeGroups[$DC.DomainName]['DomainAdmins']
                Set-ADACLOwner -ADObject $DC.DistinguishedName -Principal $Principal
                $Done = $true
            }
        }
        if ($Type -contains 'Manager') {
            if ($null -ne $DC.ManagedBy) {
                Write-Verbose -Message "Repair-WinADForestControllerInformation - Fixing (Manager) [$($DC.DomainName)]($Count/$($DCs.Count)) $($DC.DNSHostName)"
                Set-ADComputer -Identity $DC.DistinguishedName -Clear ManagedBy -Server $ForestInformation['QueryServers'][$DC.DomainName]['HostName'][0]
                $Done = $true
            }
        }
        if ($Done -eq $true) { $Fixed++ }
        if ($LimitProcessing -ne 0 -and $Fixed -eq $LimitProcessing) { break } }
}
function Set-ADACLOwner {
    [cmdletBinding(SupportsShouldProcess)]
    param([Array] $ADObject,
        [Parameter(Mandatory)][string] $Principal)
    Begin {
        if ($Principal -is [string]) {
            if ($Principal -like '*/*') {
                $SplittedName = $Principal -split '/'
                [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($SplittedName[0], $SplittedName[1])
            } else { [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal) }
        } else { return }
    }
    Process {
        foreach ($Object in $ADObject) {
            if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) {
                [string] $DistinguishedName = $Object.DistinguishedName
                [string] $CanonicalName = $Object.CanonicalName
                [string] $ObjectClass = $Object.ObjectClass
            } elseif ($Object -is [string]) {
                [string] $DistinguishedName = $Object
                [string] $CanonicalName = ''
                [string] $ObjectClass = ''
            } else {
                Write-Warning "Set-ADACLOwner - Object not recognized. Skipping..."
                continue
            }
            $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDC) -replace '=' -replace ','
            if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                Write-Verbose "Set-ADACLOwner - Enabling PSDrives for $DistinguishedName to $DNConverted"
                New-ADForestDrives -ForestName $ForestName
                if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                    Write-Warning "Set-ADACLOwner - Drive $DNConverted not mapped. Terminating..."
                    return
                }
            }
            $PathACL = "$DNConverted`:\$($DistinguishedName)"
            $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop
            $CurrentOwner = $ACLs.Owner
            Write-Verbose "Set-ADACLOwner - Changing owner from $($CurrentOwner) to $Identity for $($ACLs.Path)"
            try { $ACLs.SetOwner($Identity) } catch {
                Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $Identity for $($ACLs.Path): $($_.Exception.Message)"
                break
            }
            try { Set-Acl -Path $PathACL -AclObject $ACLs -ErrorAction Stop } catch { Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $Identity for $($ACLs.Path): $($_.Exception.Message)" }
        }
    }
    End {}
}
function Set-DnsServerIP {
    [alias('Set-WinDNSServerIP')]
    [cmdletbInding(SupportsShouldProcess)]
    param([string[]] $ComputerName,
        [string[]] $DnsIpAddress,
        [pscredential] $Credential)
    foreach ($Computer in $Computers) {
        try { if ($Credential) { $CimSession = New-CimSession -ComputerName $Computer -Credential $Credential -Authentication Negotiate -ErrorAction Stop } else { $CimSession = New-CimSession -ComputerName $Computer -ErrorAction Stop -Authentication Negotiate } } catch {
            Write-Warning "Couldn't authorize session to $Computer. Error $($_.Exception.Message). Skipping."
            continue
        }
        $Adapters = Get-CimData -Class Win32_NetworkAdapterConfiguration -ComputerName $Computer | Where-Object { $_.DHCPEnabled -ne 'True' -and $null -ne $_.DNSServerSearchOrder }
        if ($Adapters) {
            $Text = "Setting DNS to $($DNSIPAddress -join ', ')"
            if ($PSCmdlet.ShouldProcess($Computer, $Text)) {
                if ($Adapters) {
                    try { $Adapters | Set-DnsClientServerAddress -ServerAddresses $DnsIpAddress -CimSession $CimSession } catch {
                        Write-Warning "Couldn't fix adapters with IP Address for $Computer. Error $($_.Exception.Message)"
                        continue
                    }
                }
                Get-DNSServerIP -ComputerName $Computer
            }
        }
    }
}
function Set-WinADDiagnostics {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [ValidateSet('Knowledge Consistency Checker (KCC)',
            'Security Events',
            'ExDS Interface Events',
            'MAPI Interface Events',
            'Replication Events',
            'Garbage Collection',
            'Internal Configuration',
            'Directory Access',
            'Internal Processing',
            'Performance Counters',
            'Initialization / Termination',
            'Service Control',
            'Name Resolution',
            'Backup',
            'Field Engineering',
            'LDAP Interface Events',
            'Setup',
            'Global Catalog',
            'Inter-site Messaging',
            'Group Caching',
            'Linked-Value Replication',
            'DS RPC Client',
            'DS RPC Server',
            'DS Schema',
            'Transformation Engine',
            'Claims-Based Access Control',
            'Netlogon')][string[]] $Diagnostics,
        [string] $Level,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $LevelsDictionary = @{'None' = 0
        'Minimal'                = 1
        'Basic'                  = 2
        'Extensive'              = 3
        'Verbose'                = 4
        'Internal'               = 5
    }
    $Type = @{'Knowledge Consistency Checker (KCC)' = '1 Knowledge Consistency Checker'
        'Security Events'                           = '2 Security Events'
        'ExDS Interface Events'                     = '3 ExDS Interface Events'
        'MAPI Interface Events'                     = '4 MAPI Interface Events'
        'Replication Events'                        = '5 Replication Events'
        'Garbage Collection'                        = '6 Garbage Collection'
        'Internal Configuration'                    = '7 Internal Configuration'
        'Directory Access'                          = '8 Directory Access'
        'Internal Processing'                       = '9 Internal Processing'
        'Performance Counters'                      = '10 Performance Counters'
        'Initialization / Termination'              = '11 Initialization/Termination'
        'Service Control'                           = '12 Service Control'
        'Name Resolution'                           = '13 Name Resolution'
        'Backup'                                    = '14 Backup'
        'Field Engineering'                         = '15 Field Engineering'
        'LDAP Interface Events'                     = '16 LDAP Interface Events'
        'Setup'                                     = '17 Setup'
        'Global Catalog'                            = '18 Global Catalog'
        'Inter-site Messaging'                      = '19 Inter-site Messaging'
        'Group Caching'                             = '20 Group Caching'
        'Linked-Value Replication'                  = '21 Linked-Value Replication'
        'DS RPC Client'                             = '22 DS RPC Client'
        'DS RPC Server'                             = '23 DS RPC Server'
        'DS Schema'                                 = '24 DS Schema'
        'Transformation Engine'                     = '25 Transformation Engine'
        'Claims-Based Access Control'               = '26 Claims-Based Access Control'
    }
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    [Array] $Computers = $ForestInformation.ForestDomainControllers.HostName
    foreach ($Computer in $Computers) {
        foreach ($D in $Diagnostics) {
            if ($D) {
                $DiagnosticsType = $Type[$D]
                $DiagnosticsLevel = $LevelsDictionary[$Level]
                if ($null -ne $DiagnosticsType -and $null -ne $DiagnosticsLevel) {
                    Write-Verbose "Set-WinADDiagnostics - Setting $DiagnosticsType to $DiagnosticsLevel on $Computer"
                    Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' -Type REG_DWORD -Key $DiagnosticsType -Value $DiagnosticsLevel -ComputerName $Computer
                } else {
                    if ($D -eq 'Netlogon') {
                        if ($Level -eq 'None') {
                            Write-Verbose "Set-WinADDiagnostics - Setting Netlogon Diagnostics to Enabled on $Computer"
                            Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -Type REG_DWORD -Key 'DbFlag' -Value 0 -ComputerName $Computer -Verbose:$false
                        } else {
                            Write-Verbose "Set-WinADDiagnostics - Setting Netlogon Diagnostics to Disabled on $Computer"
                            Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -Type REG_DWORD -Key 'DbFlag' -Value 545325055 -ComputerName $Computer -Verbose:$false
                        }
                    }
                }
            }
        }
    }
}
[scriptblock] $LevelAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    @('None', 'Minimal', 'Basic', 'Extensive', 'Verbose', 'Internal') }
Register-ArgumentCompleter -CommandName Set-WinADDiagnostics -ParameterName Level -ScriptBlock $LevelAutoCompleter
function Set-WinADReplication {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [int] $ReplicationInterval = 15,
        [switch] $Instant,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    $QueryServer = $ForestInformation.QueryServers['Forest']['HostName'][0]
    $NamingContext = (Get-ADRootDSE -Server $QueryServer).configurationNamingContext
    Get-ADObject -LDAPFilter "(objectCategory=sitelink)" â€“SearchBase $NamingContext -Properties options, replInterval -Server $QueryServer | ForEach-Object { if ($Instant) {
            Set-ADObject $_ -Replace @{replInterval = $ReplicationInterval } -Server $QueryServer
            Set-ADObject $_ â€“Replace @{options = $($_.options -bor 1) } -Server $QueryServer
        } else { Set-ADObject $_ -Replace @{replInterval = $ReplicationInterval } -Server $QueryServer } }
}
function Set-WinADReplicationConnections {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [switch] $Force,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    [Flags()]
    enum ConnectionOption {
        None
        IsGenerated
        TwoWaySync
        OverrideNotifyDefault = 4
        UseNotify = 8
        DisableIntersiteCompression = 16
        UserOwnedSchedule = 32
        RodcTopology = 64
    }
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    $QueryServer = $ForestInformation.QueryServers['Forest']['HostName'][0]
    $NamingContext = (Get-ADRootDSE -Server $QueryServer).configurationNamingContext
    $Connections = Get-ADObject â€“SearchBase $NamingContext -LDAPFilter "(objectCategory=ntDSConnection)" -Properties * -Server $QueryServer
    foreach ($_ in $Connections) {
        $OptionsTranslated = [ConnectionOption] $_.Options
        if ($OptionsTranslated -like '*IsGenerated*' -and -not $Force) { Write-Verbose "Set-WinADReplicationConnections - Skipping $($_.CN) automatically generated link" } else {
            Write-Verbose "Set-WinADReplicationConnections - Changing $($_.CN)"
            Set-ADObject $_ â€“Replace @{options = $($_.options -bor 8) } -Server $QueryServer
        }
    }
}
function Set-WinADShare {
    [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Type')]
    param([string] $Path,
        [validateset('NetLogon')][string[]] $ShareType,
        [switch] $Owner,
        [Parameter(ParameterSetName = 'Principal', Mandatory)][string] $Principal,
        [Parameter(ParameterSetName = 'Type', Mandatory)]
        [validateset('Default')][string[]] $Type)
    if ($ShareType) {
        $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        foreach ($Domain in $ForestInformation.Domains) {
            $Path = -join ("\\", $Domain, "\$ShareType")
            @(Get-Item -Path $Path) + @(Get-ChildItem -Path $Path -Recurse:$true) | ForEach-Object -Process { if ($Owner) { Get-FileOwner -JustPath -Path $_ -Resolve } else { Get-FilePermission -Path $_ -ResolveTypes -Extended } }
        }
    } else {
        if ($Path -and (Test-Path -Path $Path)) {
            @(Get-Item -Path $Path) + @(Get-ChildItem -Path $Path -Recurse:$true) | ForEach-Object -Process { if ($Owner) {
                    $IdentityOwner = Get-FileOwner -JustPath -Path $_.FullName -Resolve
                    if ($PSCmdlet.ParameterSetName -eq 'Principal') {} else { if ($IdentityOwner.OwnerSid -ne 'S-1-5-32-544') { Set-FileOwner -Path $Path -JustPath -Owner 'S-1-5-32-544' } else { Write-Verbose "Set-WinADShare - Owner of $($_.FullName) already set to $($IdentityOwner.OwnerName). Skipping." } }
                } else { Get-FilePermission -Path $_ -ResolveTypes -Extended } }
        }
    }
}
function Set-WinADTombstoneLifetime {
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [int] $Days = 180,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    $QueryServer = $ForestInformation.QueryServers['Forest']['HostName'][0]
    $Partition = $((Get-ADRootDSE -Server $QueryServer).configurationNamingContext)
    Set-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$Partition" -Partition $Partition -Replace @{tombstonelifetime = $Days } -Server $QueryServer
}
function Show-WinADGroupMember {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Identity
    Group Name to search for
 
    .PARAMETER Conditions
    Provides ability to control look and feel of tables across HTML
 
    .PARAMETER FilePath
    Path to HTML file where it's saved. If not given temporary path is used
 
    .PARAMETER HideAppliesTo
    Allows to define to which diagram HideComputers,HideUsers,HideOther applies to
 
    .PARAMETER HideComputers
    Hide computers from diagrams - useful for performance reasons
 
    .PARAMETER HideUsers
    Hide users from diagrams - useful for performance reasons
 
    .PARAMETER HideOther
    Hide other objects from diagrams - useful for performance reasons
 
    .PARAMETER Online
    Forces use of online CDN for JavaScript/CSS which makes the file smaller. Default - use offline.
 
    .PARAMETER HideHTML
    Prevents HTML from opening up after command is done. Useful for automation
 
    .PARAMETER DisableBuiltinConditions
    Disables table coloring allowing user to define it's own conditions
 
    .PARAMETER AdditionalStatistics
    Adds additional data to Self object. It includes count for NestingMax, NestingGroup, NestingGroupSecurity, NestingGroupDistribution. It allows for easy filtering where we expect security groups only when there are nested distribution groups.
 
    .PARAMETER Summary
    Adds additional tab with all groups together on two diagrams
 
    .PARAMETER SummaryOnly
    Adds one tab with all groups together on two diagrams
 
    .EXAMPLE
   Show-WinADGroupMember -GroupName 'Domain Admins' -FilePath $PSScriptRoot\Reports\GroupMembership1.html -Online -Verbose
 
   .EXAMPLE
   Show-WinADGroupMember -GroupName 'Test-Group', 'Domain Admins' -FilePath $PSScriptRoot\Reports\GroupMembership2.html -Online -Verbose
 
   .EXAMPLE
   Show-WinADGroupMember -GroupName 'GDS-TestGroup4' -FilePath $PSScriptRoot\Reports\GroupMembership3.html -Summary -Online -Verbose
 
   .EXAMPLE
   Show-WinADGroupMember -GroupName 'Group1' -Verbose -Online
 
    .NOTES
    General notes
    #>

    [alias('Show-ADGroupMember')]
    [cmdletBinding(DefaultParameterSetName = 'Default')]
    param([Parameter(Position = 0)][alias('GroupName', 'Group')][Array] $Identity,
        [Parameter(Position = 1)][scriptblock] $Conditions,
        [string] $FilePath,
        [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both',
        [switch] $HideComputers,
        [switch] $HideUsers,
        [switch] $HideOther,
        [switch] $Online,
        [switch] $HideHTML,
        [switch] $DisableBuiltinConditions,
        [switch] $AdditionalStatistics,
        [Parameter(ParameterSetName = 'Default')][switch] $Summary,
        [Parameter(ParameterSetName = 'SummaryOnly')][switch] $SummaryOnly)
    $VisualizeOnly = $false
    if ($FilePath -eq '') { $FilePath = Get-FileName -Extension 'html' -Temporary }
    $GroupsList = [System.Collections.Generic.List[object]]::new()
    New-HTML -TitleText "Visual Group Membership" { New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
        New-HTMLTableOption -DataStore JavaScript
        New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey
        if ($Identity[0].GroupName) {
            $GroupMembersCache = [ordered] @{}
            $VisualizeOnly = $true
            foreach ($Entry in $Identity) {
                $IdentityGroupName = "($($Entry.GroupName) / $($Entry.GroupDomainName))"
                if (-not $GroupMembersCache[$IdentityGroupName]) { $GroupMembersCache[$IdentityGroupName] = [System.Collections.Generic.List[PSCustomObject]]::new() }
                $GroupMembersCache[$IdentityGroupName].Add($Entry)
            }
            [Array] $IdentityList = $GroupMembersCache.Keys
        } else { [Array] $IdentityList = $Identity }
        foreach ($Group in $IdentityList) {
            try {
                Write-Verbose "Show-WinADGroupMember - requesting $Group group nested membership"
                if ($VisualizeOnly) { $ADGroup = $GroupMembersCache[$Group] } else { $ADGroup = Get-WinADGroupMember -Group $Group -All -AddSelf -AdditionalStatistics:$AdditionalStatistics }
                if ($Summary -or $SummaryOnly) { foreach ($Object in $ADGroup) { $GroupsList.Add($Object) } }
            } catch {
                Write-Warning "Show-WinADGroupMember - Error processing group $Group. Skipping. Needs investigation why it failed. Error: $($_.Exception.Message)"
                continue
            }
            if ($ADGroup -and -not $SummaryOnly) {
                $GroupName = $ADGroup[0].GroupName
                $NetBIOSName = Convert-DomainFqdnToNetBIOS -DomainName $ADGroup[0].DomainName
                $FullName = "$NetBIOSName\$GroupName"
                $DataStoreID = -join ('table', (Get-RandomStringName -Size 10 -ToLower))
                $DataTableID = -join ('table', (Get-RandomStringName -Size 10 -ToLower))
                New-HTMLTab -TabName $FullName { New-HTMLTab -TabName 'Information' { New-HTMLSection -Title "Information for $GroupName" { New-HTMLTable -DataTable $ADGroup -Filtering -DataStoreID $DataStoreID { if (-not $DisableBuiltinConditions) {
                                    New-TableHeader -Names Name, SamAccountName, DomainName, DisplayName -Title 'Member'
                                    New-TableHeader -Names DirectMembers, DirectGroups, IndirectMembers, TotalMembers -Title 'Statistics'
                                    New-TableHeader -Names GroupType, GroupScope -Title 'Group Details'
                                    New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $false -Name Enabled -Operator eq
                                    New-TableCondition -BackgroundColor LightBlue -ComparisonType string -Value '' -Name ParentGroup -Operator eq -Row
                                    New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name CrossForest -Operator eq
                                    New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name CircularIndirect -Operator eq -Row
                                    New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name CircularDirect -Operator eq -Row
                                }
                                if ($Conditions) { & $Conditions } } } }
                    New-HTMLTab -TabName 'Diagram Basic' { New-HTMLSection -Title "Diagram for $GroupName" { New-HTMLGroupDiagramDefault -ADGroup $ADGroup -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -DataTableID $DataTableID -ColumnID 1 -Online:$Online } }
                    New-HTMLTab -TabName 'Diagram Hierarchy' { New-HTMLSection -Title "Diagram for $GroupName" { New-HTMLGroupDiagramHierachical -ADGroup $ADGroup -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -Online:$Online } } }
            }
        }
        if ($Summary -or $SummaryOnly) {
            New-HTMLTab -Name 'Summary' { New-HTMLTab -TabName 'Diagram Basic' { New-HTMLSection -Title "Diagram for Summary" { New-HTMLGroupDiagramSummary -ADGroup $GroupsList -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -DataTableID $DataTableID -ColumnID 1 -Online:$Online } }
                New-HTMLTab -TabName 'Diagram Hierarchy' { New-HTMLSection -Title "Diagram for Summary" { New-HTMLGroupDiagramSummaryHierarchical -ADGroup $GroupsList -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -Online:$Online } } }
        } } -Online:$Online -FilePath $FilePath -ShowHTML:(-not $HideHTML)
}
function Show-WinADGroupMemberOf {
    [alias('Show-ADGroupMemberOf')]
    [cmdletBinding(DefaultParameterSetName = 'Default')]
    param([Parameter(Position = 1)][scriptblock] $Conditions,
        [parameter(Position = 0, Mandatory)][string[]] $Identity,
        [string] $FilePath,
        [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both',
        [Parameter(ParameterSetName = 'Default')][switch] $Summary,
        [Parameter(ParameterSetName = 'SummaryOnly')][switch] $SummaryOnly,
        [switch] $Online,
        [switch] $HideHTML,
        [switch] $DisableBuiltinConditions)
    if ($FilePath -eq '') { $FilePath = Get-FileName -Extension 'html' -Temporary }
    $GroupsList = [System.Collections.Generic.List[object]]::new()
    New-HTML -TitleText "Visual Object MemberOf" { New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
        New-HTMLTableOption -DataStore JavaScript
        New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey
        foreach ($ADObject in $Identity) {
            try {
                Write-Verbose "Show-WinADObjectMember - requesting $Identity member of property"
                $MyObject = Get-WinADGroupMemberOf -Identity $ADObject -AddSelf
                if ($Summary -or $SummaryOnly) { foreach ($Object in $MyObject) { $GroupsList.Add($Object) } }
            } catch {
                Write-Warning "Show-WinADGroupMemberOf - Error processing group $Group. Skipping. Needs investigation why it failed. Error: $($_.Exception.Message)"
                continue
            }
            if ($MyObject -and -not $SummaryOnly) {
                $ObjectName = $MyObject[0].ObjectName
                $DataStoreID = -join ('table', (Get-RandomStringName -Size 10 -ToLower))
                $DataTableID = -join ('table', (Get-RandomStringName -Size 10 -ToLower))
                New-HTMLTab -TabName $ObjectName { New-HTMLTab -TabName 'Information' { New-HTMLSection -Title "Information for $ObjectName" { New-HTMLTable -DataTable $MyObject -Filtering -DataStoreID $DataStoreID { if (-not $DisableBuiltinConditions) {
                                    New-TableHeader -Names Name, SamAccountName, DomainName, DisplayName -Title 'Member'
                                    New-TableHeader -Names GroupType, GroupScope -Title 'Group Details'
                                    New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $false -Name Enabled -Operator eq
                                    New-TableCondition -BackgroundColor LightBlue -ComparisonType string -Value '' -Name ParentGroup -Operator eq -Row
                                    New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name CrossForest -Operator eq
                                    New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name Circular -Operator eq -Row
                                }
                                if ($Conditions) { & $Conditions } } } }
                    New-HTMLTab -TabName 'Diagram Basic' { New-HTMLSection -Title "Diagram for $ObjectName" { New-HTMLGroupOfDiagramDefault -Identity $MyObject -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -DataTableID $DataTableID -ColumnID 1 -Online:$Online } }
                    New-HTMLTab -TabName 'Diagram Hierarchy' { New-HTMLSection -Title "Diagram for $ObjectName" { New-HTMLGroupOfDiagramHierarchical -Identity $MyObject -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -Online:$Online } } }
            }
        }
        if ($Summary -or $SummaryOnly) {
            New-HTMLTab -Name 'Summary' { New-HTMLTab -TabName 'Diagram Basic' { New-HTMLSection -Title "Diagram for Summary" { New-HTMLGroupOfDiagramSummary -ADGroup $GroupsList -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -DataTableID $DataTableID -ColumnID 1 -Online:$Online } }
                New-HTMLTab -TabName 'Diagram Hierarchy' { New-HTMLSection -Title "Diagram for Summary" { New-HTMLGroupOfDiagramSummaryHierarchical -ADGroup $GroupsList -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -Online:$Online } } }
        } } -Online:$Online -FilePath $FilePath -ShowHTML:(-not $HideHTML)
}
function Show-WinADTrust {
    [alias('Show-ADTrust', 'Show-ADTrusts', 'Show-WinADTrusts')]
    [cmdletBinding()]
    param([Parameter(Position = 0)][scriptblock] $Conditions,
        [switch] $Recursive,
        [string] $FilePath,
        [switch] $Online,
        [switch] $HideHTML,
        [switch] $DisableBuiltinConditions,
        [switch] $PassThru)
    if ($FilePath -eq '') { $FilePath = Get-FileName -Extension 'html' -Temporary }
    $Script:ADTrusts = @()
    New-HTML -TitleText "Visual Trusts" { New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
        New-HTMLTableOption -DataStore HTML
        New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey
        $Script:ADTrusts = Get-WinADTrust -Recursive:$Recursive
        Write-Verbose "Show-WinADTrust - Found $($ADTrusts.Count) trusts"
        New-HTMLTab -TabName 'Summary' { New-HTMLSection -HeaderText 'Trusts Diagram' { New-HTMLDiagram -Height 'calc(50vh)' { New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion
                    foreach ($Node in $AllNodes) { New-DiagramNode -Label $Node.'Trust' }
                    foreach ($Trust in $ADTrusts) {
                        New-DiagramNode -Label $Trust.'TrustSource' -IconSolid audio-description
                        New-DiagramNode -Label $Trust.'TrustTarget' -IconSolid audio-description
                        $newDiagramLinkSplat = @{From = $Trust.'TrustSource'
                            To                        = $Trust.'TrustTarget'
                            ColorOpacity              = 0.7
                        }
                        if ($Trust.'TrustDirection' -eq 'Disabled') {} elseif ($Trust.'TrustDirection' -eq 'Inbound') { $newDiagramLinkSplat.ArrowsFromEnabled = $true } elseif ($Trust.'TrustDirection' -eq 'Outbount') {
                            $newDiagramLinkSplat.ArrowsToEnabled = $true
                            New-DiagramLink @newDiagramLinkSplat
                        } elseif ($Trust.'TrustDirection' -eq 'Bidirectional') {
                            $newDiagramLinkSplat.ArrowsToEnabled = $true
                            $newDiagramLinkSplat.ArrowsFromEnabled = $true
                        }
                        if ($Trust.IntraForest) { $newDiagramLinkSplat.Color = 'DarkSpringGreen' }
                        if ($Trust.QueryStatus -eq 'OK' -or $Trust.TrustStatus -eq 'OK') {
                            $newDiagramLinkSplat.Dashes = $false
                            $newDiagramLinkSplat.FontColor = 'Green'
                        } else {
                            $newDiagramLinkSplat.Dashes = $true
                            $newDiagramLinkSplat.FontColor = 'Red'
                        }
                        if ($Trust.IsTGTDelegationEnabled) {
                            $newDiagramLinkSplat.Color = 'Red'
                            $newDiagramLinkSplat.Label = "Delegation Enabled"
                        } else { $newDiagramLinkSplat.Label = $Trust.QueryStatus }
                        New-DiagramLink @newDiagramLinkSplat
                    } } }
            New-HTMLSection -Title "Information about Trusts" { New-HTMLTable -DataTable $ADTrusts -Filtering { if (-not $DisableBuiltinConditions) {
                        New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType string -Value 'OK' -Name TrustStatus -Operator eq
                        New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType string -Value 'OK' -Name QueryStatus -Operator eq
                        New-TableCondition -BackgroundColor CoralRed -ComparisonType string -Value 'NOT OK' -Name QueryStatus -Operator eq
                        New-TableCondition -BackgroundColor CoralRed -ComparisonType bool -Value $true -Name IsTGTDelegationEnabled -Operator eq
                    }
                    if ($Conditions) { & $Conditions } } -DataTableID 'DT-TrustsInformation' } }
        $TrustCache = [ordered]@{}
        foreach ($Trust in $ADTrusts) {
            Write-Verbose "Show-WinADTrust - Processing $($Trust.TrustSource) to $($Trust.TrustTarget)"
            if (-not $TrustCache[$Trust.TrustSource]) {
                Write-Verbose "Show-WinADTrust - Creating cache for $($Trust.TrustSource)"
                $TrustCache[$Trust.TrustSource] = [System.Collections.Generic.List[PSCustomObject]]::new()
            }
            $TrustCache[$Trust.TrustSource].Add($Trust)
        }
        foreach ($Source in $TrustCache.Keys) {
            New-HTMLTab -TabName "Source $($Source.ToUpper())" { foreach ($Trust in $TrustCache[$Source]) {
                    if ($Trust.QueryStatus -eq 'OK' -or $Trust.TrustStatus -eq 'OK') {
                        $IconColor = 'MediumSeaGreen'
                        $IconSolid = 'smile'
                    } else {
                        $IconColor = 'CoralRed'
                        $IconSolid = 'angry'
                    }
                    New-HTMLTab -TabName "Target $($Trust.TrustTarget.ToUpper())" -IconColor $IconColor -IconSolid $IconSolid -TextColor $IconColor { New-HTMLSection -Invisible { New-HTMLSection -Title "Trust Information" { New-HTMLTable -DataTable $Trust { New-TableHeader -Names Name, Value -Title 'Trust Information' } -Transpose -HideFooter -DisablePaging -Buttons copyHtml5, excelHtml5, pdfHtml5 }
                            New-HTMLSection -Invisible -Wrap wrap { New-HTMLSection -Title "Name suffix status" { New-HTMLTable -DataTable $Trust.AdditionalInformation.msDSTrustForestTrustInfo -Filtering { if ($Trust.AdditionalInformation.msDSTrustForestTrustInfo.Count -gt 0) {
                                            New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType string -Value 'Enabled' -Name Status -Operator eq -Row
                                            New-TableCondition -BackgroundColor CoralRed -ComparisonType string -Value 'Enabled' -Name Status -Operator ne -Row
                                        } } }
                                New-HTMLSection -Title "Name suffix routing (include)" { New-HTMLTable -DataTable $Trust.AdditionalInformation.SuffixesInclude -Filtering { if ($Trust.AdditionalInformation.SuffixesInclude.Count -gt 0) {
                                            New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType string -Value 'Enabled' -Name Status -Operator eq -Row
                                            New-TableCondition -BackgroundColor CoralRed -ComparisonType string -Value 'Enabled' -Name Status -Operator ne -Row
                                        } } }
                                New-HTMLSection -Title "Name suffix routing (exclude)" { New-HTMLTable -DataTable $Trust.AdditionalInformation.SuffixesExclude -Filtering { if ($Trust.AdditionalInformation.SuffixesExclude.Count -gt 0) {
                                            New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType string -Value 'Enabled' -Name Status -Operator eq -Row
                                            New-TableCondition -BackgroundColor CoralRed -ComparisonType string -Value 'Enabled' -Name Status -Operator ne -Row
                                        } } } } } }
                } }
        } } -Online:$Online -FilePath $FilePath -ShowHTML:(-not $HideHTML)
    if ($PassThru) { $Script:ADTrusts }
}
function Sync-DomainController {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $DistinguishedName = (Get-ADDomain -Server $QueryServer).DistinguishedName
        ($ForestInformation['DomainDomainControllers']["$Domain"]).Name | ForEach-Object { Write-Verbose -Message "Sync-DomainController - Forcing synchronization $_"
            repadmin /syncall $_ $DistinguishedName /e /A | Out-Null }
    }
}
function Test-ADDomainController {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'DomainController', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [Parameter(Mandatory = $false)][PSCredential] $Credential = $null,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $CredentialParameter = @{}
    if ($null -ne $Credential) { $CredentialParameter['Credential'] = $Credential }
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    $Output = foreach ($Computer in $ForestInformation.ForestDomainControllers.HostName) {
        $Result = Invoke-Command -ComputerName $Computer -ScriptBlock { dcdiag.exe /v /c /Skip:OutboundSecureChannels } @CredentialParameter
        for ($Line = 0; $Line -lt $Result.length; $Line++) {
            if ($Result[$Line] -match '^\s{9}.{25} (\S+) (\S+) test$') { $Result[$Line] = $Result[$Line] + ' ' + $Result[$Line + 2].Trim() }
            if ($Result[$Line] -match '^\s{6}Starting test: \S+$') { $LineStart = $Line }
            if ($Result[$Line] -match '^\s{9}.{25} (\S+) (\S+) test (\S+)$') {
                $DiagnosticResult = [PSCustomObject] @{ComputerName = $Computer
                    Target                                          = $Matches[1]
                    Test                                            = $Matches[3]
                    Result                                          = $Matches[2] -eq 'passed'
                    Data                                            = $Result[$LineStart..$Line] -join [System.Environment]::NewLine
                }
                $DiagnosticResult
            }
        }
    }
    $Output
}
function Test-ADRolesAvailability {
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $Roles = Get-WinADForestRoles -Forest $Forest -IncludeDomains $IncludeDomains -IncludeDomainControllers $IncludeDomainControllers -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    if ($IncludeDomains) {
        [PSCustomObject] @{PDCEmulator       = $Roles['PDCEmulator']
            PDCEmulatorAvailability          = if ($Roles['PDCEmulator']) { (Test-NetConnection -ComputerName $Roles['PDCEmulator']).PingSucceeded } else { $false }
            RIDMaster                        = $Roles['RIDMaster']
            RIDMasterAvailability            = if ($Roles['RIDMaster']) { (Test-NetConnection -ComputerName $Roles['RIDMaster']).PingSucceeded } else { $false }
            InfrastructureMaster             = $Roles['InfrastructureMaster']
            InfrastructureMasterAvailability = if ($Roles['InfrastructureMaster']) { (Test-NetConnection -ComputerName $Roles['InfrastructureMaster']).PingSucceeded } else { $false }
        }
    } else {
        [PSCustomObject] @{SchemaMaster    = $Roles['SchemaMaster']
            SchemaMasterAvailability       = if ($Roles['SchemaMaster']) { (Test-NetConnection -ComputerName $Roles['SchemaMaster']).PingSucceeded } else { $false }
            DomainNamingMaster             = $Roles['DomainNamingMaster']
            DomainNamingMasterAvailability = if ($Roles['DomainNamingMaster']) { (Test-NetConnection -ComputerName $Roles['DomainNamingMaster']).PingSucceeded } else { $false }
        }
    }
}
function Test-ADSiteLinks {
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string] $Splitter,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation
    if (($ForestInformation.ForestDomainControllers).Count -eq 1) {
        [ordered] @{SiteLinksManual             = 'No sitelinks, single DC'
            SiteLinksAutomatic                  = 'No sitelinks, single DC'
            SiteLinksCrossSiteUseNotify         = 'No sitelinks, single DC'
            SiteLinksCrossSiteNotUseNotify      = 'No sitelinks, single DC'
            SiteLinksSameSiteUseNotify          = 'No sitelinks, single DC'
            SiteLinksSameSiteNotUseNotify       = 'No sitelinks, single DC'
            SiteLinksDisabled                   = 'No sitelinks, single DC'
            SiteLinksEnabled                    = 'No sitelinks, single DC'
            SiteLinksCrossSiteUseNotifyCount    = 0
            SiteLinksCrossSiteNotUseNotifyCount = 0
            SiteLinksSameSiteUseNotifyCount     = 0
            SiteLinksSameSiteNotUseNotifyCount  = 0
            SiteLinksManualCount                = 0
            SiteLinksAutomaticCount             = 0
            SiteLinksDisabledCount              = 0
            SiteLinksEnabledCount               = 0
            SiteLinksTotalCount                 = 0
            SiteLinksTotalActiveCount           = 0
            Comment                             = 'No sitelinks, single DC'
        }
    } else {
        [Array] $SiteLinks = Get-WinADSiteConnections -ExtendedForestInformation $ForestInformation
        if ($SiteLinks) {
            $Collection = @($SiteLinks).Where( { $_.Options -notcontains 'IsGenerated' -and $_.EnabledConnection -eq $true }, 'Split')
            [Array] $LinksManual = foreach ($Link in $Collection[0]) { "$($Link.ServerFrom) to $($Link.ServerTo)" }
            [Array] $LinksAutomatic = foreach ($Link in $Collection[1]) { "$($Link.ServerFrom) to $($Link.ServerTo)" }
            $LinksUsingNotificationsUnnessecary = [System.Collections.Generic.List[string]]::new()
            $LinksUsingNotifications = [System.Collections.Generic.List[string]]::new()
            $LinksNotUsingNotifications = [System.Collections.Generic.List[string]]::new()
            $LinksUsingNotificationsWhichIsOk = [System.Collections.Generic.List[string]]::new()
            $DisabledLinks = [System.Collections.Generic.List[string]]::new()
            $EnabledLinks = [System.Collections.Generic.List[string]]::new()
            foreach ($Link in $SiteLinks) {
                if ($Link.EnabledConnection -eq $true) { $EnabledLinks.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } else { $DisabledLinks.Add("$($Link.ServerFrom) to $($Link.ServerTo)") }
                if ($Link.SiteFrom -eq $Link.SiteTo) { if ($Link.Options -contains 'UseNotify') { $LinksUsingNotificationsUnnessecary.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } else { $LinksUsingNotificationsWhichIsOk.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } } else { if ($Link.Options -contains 'UseNotify') { $LinksUsingNotifications.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } else { $LinksNotUsingNotifications.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } }
            }
            [ordered] @{SiteLinksManual             = if ($Splitter -eq '') { $LinksManual } else { $LinksManual -join $Splitter }
                SiteLinksAutomatic                  = if ($Splitter -eq '') { $LinksAutomatic } else { $LinksAutomatic -join $Splitter }
                SiteLinksCrossSiteUseNotify         = if ($Splitter -eq '') { $LinksUsingNotifications } else { $LinksUsingNotifications -join $Splitter }
                SiteLinksCrossSiteNotUseNotify      = if ($Splitter -eq '') { $LinksNotUsingNotifications } else { $LinksNotUsingNotifications -join $Splitter }
                SiteLinksSameSiteUseNotify          = if ($Splitter -eq '') { $LinksUsingNotificationsUnnessecary } else { $LinksUsingNotificationsUnnessecary -join $Splitter }
                SiteLinksSameSiteNotUseNotify       = if ($Splitter -eq '') { $LinksUsingNotificationsWhichIsOk } else { $LinksUsingNotificationsWhichIsOk -join $Splitter }
                SiteLinksDisabled                   = if ($Splitter -eq '') { $DisabledLinks } else { $DisabledLinks -join $Splitter }
                SiteLinksEnabled                    = if ($Splitter -eq '') { $EnabledLinks } else { $EnabledLinks -join $Splitter }
                SiteLinksCrossSiteUseNotifyCount    = $LinksUsingNotifications.Count
                SiteLinksCrossSiteNotUseNotifyCount = $LinksNotUsingNotifications.Count
                SiteLinksSameSiteUseNotifyCount     = $LinksUsingNotificationsUnnessecary.Count
                SiteLinksSameSiteNotUseNotifyCount  = $LinksUsingNotificationsWhichIsOk.Count
                SiteLinksManualCount                = $Collection[0].Count
                SiteLinksAutomaticCount             = $Collection[1].Count
                SiteLinksDisabledCount              = $DisabledLinks.Count
                SiteLinksEnabledCount               = $EnabledLinks.Count
                SiteLinksTotalCount                 = $SiteLinks.Count
                SiteLinksTotalActiveCount           = ($SiteLinks | Where-Object { $_.EnabledConnection -eq $true }).Count
                Comment                             = 'OK'
            }
        } else {
            [ordered] @{SiteLinksManual             = 'No sitelinks'
                SiteLinksAutomatic                  = 'No sitelinks'
                SiteLinksCrossSiteUseNotify         = 'No sitelinks'
                SiteLinksCrossSiteNotUseNotify      = 'No sitelinks'
                SiteLinksSameSiteUseNotify          = 'No sitelinks'
                SiteLinksSameSiteNotUseNotify       = 'No sitelinks'
                SiteLinksDisabled                   = 'No sitelinks'
                SiteLinksEnabled                    = 'No sitelinks'
                SiteLinksCrossSiteUseNotifyCount    = 0
                SiteLinksCrossSiteNotUseNotifyCount = 0
                SiteLinksSameSiteUseNotifyCount     = 0
                SiteLinksSameSiteNotUseNotifyCount  = 0
                SiteLinksManualCount                = 0
                SiteLinksAutomaticCount             = 0
                SiteLinksDisabledCount              = 0
                SiteLinksEnabledCount               = 0
                SiteLinksTotalCount                 = 0
                SiteLinksTotalActiveCount           = 0
                Comment                             = 'Error'
            }
        }
    }
}
function Test-DNSNameServers {
    [cmdletBinding()]
    param([string] $DomainController,
        [string] $Domain)
    if ($DomainController) {
        $AllDomainControllers = (Get-ADDomainController -Server $Domain -Filter { IsReadOnly -eq $false }).HostName
        try {
            $Hosts = Get-DnsServerResourceRecord -ZoneName $Domain -ComputerName $DomainController -RRType NS -ErrorAction Stop
            $NameServers = (($Hosts | Where-Object { $_.HostName -eq '@' }).RecordData.NameServer) -replace ".$"
            $Compare = ((Compare-Object -ReferenceObject $AllDomainControllers -DifferenceObject $NameServers -IncludeEqual).SideIndicator -notin @('=>', '<='))
            [PSCustomObject] @{DomainControllers = $AllDomainControllers
                NameServers                      = $NameServers
                Status                           = $Compare
                Comment                          = "Name servers found $($NameServers -join ', ')"
            }
        } catch {
            [PSCustomObject] @{DomainControllers = $AllDomainControllers
                NameServers                      = $null
                Status                           = $false
                Comment                          = $_.Exception.Message
            }
        }
    }
}
function Test-FSMORolesAvailability {
    [cmdletBinding()]
    param([string] $Domain = $Env:USERDNSDOMAIN)
    $DC = Get-ADDomainController -Server $Domain -Filter *
    $Output = foreach ($S in $DC) {
        if ($S.OperationMasterRoles.Count -gt 0) { $Status = Test-Connection -ComputerName $S.HostName -Count 2 -Quiet } else { $Status = $null }
        foreach ($_ in $S.OperationMasterRoles) {
            [PSCustomObject] @{Role = $_
                HostName            = $S.HostName
                Status              = $Status
            }
        }
    }
    $Output
}
Function Test-LDAP {
    <#
    .SYNOPSIS
    Tests LDAP connectivity to one ore more servers.
 
    .DESCRIPTION
    Tests LDAP connectivity to one ore more servers. It's able to gather certificate information which provides useful information.
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExcludeDomainControllers
    Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored.
 
    .PARAMETER IncludeDomainControllers
    Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored.
 
    .PARAMETER SkipRODC
    Skip Read-Only Domain Controllers. By default all domain controllers are included.
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER ComputerName
    Provide FQDN, IpAddress or NetBIOS name to test LDAP connectivity. This can be used instead of targetting Forest/Domain specific LDAP Servers
 
    .PARAMETER GCPortLDAP
    Global Catalog Port for LDAP. If not defined uses default 3268 port.
 
    .PARAMETER GCPortLDAPSSL
    Global Catalog Port for LDAPs. If not defined uses default 3269 port.
 
    .PARAMETER PortLDAP
    LDAP port. If not defined uses default 389
 
    .PARAMETER PortLDAPS
    LDAPs port. If not defined uses default 636
 
    .PARAMETER VerifyCertificate
    Binds to LDAP and gathers information about certificate available
 
    .PARAMETER Credential
    Allows to define credentials. This switches authentication for LDAP Binding from Kerberos to Basic
 
    .EXAMPLE
    Test-LDAP -ComputerName 'AD1' -VerifyCertificate | Format-Table *
 
    .EXAMPLE
    Test-LDAP -VerifyCertificate -SkipRODC | Format-Table *
 
    .NOTES
    General notes
    #>

    [CmdletBinding(DefaultParameterSetName = 'Forest')]
    param ([Parameter(ParameterSetName = 'Forest')][alias('ForestName')][string] $Forest,
        [Parameter(ParameterSetName = 'Forest')][string[]] $ExcludeDomains,
        [Parameter(ParameterSetName = 'Forest')][string[]] $ExcludeDomainControllers,
        [Parameter(ParameterSetName = 'Forest')][alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [Parameter(ParameterSetName = 'Forest')][alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [Parameter(ParameterSetName = 'Forest')][switch] $SkipRODC,
        [Parameter(ParameterSetName = 'Forest')][System.Collections.IDictionary] $ExtendedForestInformation,
        [alias('Server', 'IpAddress')][Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline, Mandatory, ParameterSetName = 'Computer')][string[]]$ComputerName,
        [Parameter(ParameterSetName = 'Forest')]
        [Parameter(ParameterSetName = 'Computer')]
        [int] $GCPortLDAP = 3268,
        [Parameter(ParameterSetName = 'Forest')]
        [Parameter(ParameterSetName = 'Computer')]
        [int] $GCPortLDAPSSL = 3269,
        [Parameter(ParameterSetName = 'Forest')]
        [Parameter(ParameterSetName = 'Computer')]
        [int] $PortLDAP = 389,
        [Parameter(ParameterSetName = 'Forest')]
        [Parameter(ParameterSetName = 'Computer')]
        [int] $PortLDAPS = 636,
        [Parameter(ParameterSetName = 'Forest')]
        [Parameter(ParameterSetName = 'Computer')]
        [switch] $VerifyCertificate,
        [Parameter(ParameterSetName = 'Forest')]
        [Parameter(ParameterSetName = 'Computer')]
        [PSCredential] $Credential)
    begin {
        Add-Type -Assembly System.DirectoryServices.Protocols
        if (-not $ComputerName) { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -SkipRODC:$SkipRODC.IsPresent -IncludeDomainControllers $IncludeDomainControllers -ExcludeDomainControllers $ExcludeDomainControllers }
    }
    Process {
        if ($ComputerName) {
            foreach ($Computer in $ComputerName) {
                Write-Verbose "Test-LDAP - Processing $Computer"
                $ServerName = ConvertTo-ComputerFQDN -Computer $Computer
                Test-LdapServer -ServerName $ServerName -Computer $Computer
            }
        } else {
            foreach ($Computer in $ForestInformation.ForestDomainControllers) {
                Write-Verbose "Test-LDAP - Processing $($Computer.HostName)"
                Test-LdapServer -ServerName $($Computer.HostName) -Computer $Computer.HostName -Advanced $Computer
            }
        }
    }
}
$ModuleFunctions = @{ActiveDirectory = @{'Add-ADACL' = ''
        'Get-ADACL'                                  = ''
        'Get-ADACLOwner'                             = ''
        'Get-WinADBitlockerLapsSummary'              = ''
        'Get-WinADDFSHealth'                         = ''
        'Get-WinADDiagnostics'                       = ''
        'Get-WinADDomain'                            = ''
        'Get-WinADDuplicateObject'                   = 'Get-WinADForestObjectsConflict'
        'Get-WinADForestOptionalFeatures'            = ''
        'Get-WinADForestReplication'                 = ''
        'Get-WinADForestRoles'                       = 'Get-WinADRoles', 'Get-WinADDomainRoles'
        'Get-WinADForestSchemaProperties'            = ''
        'Get-WinADForestSites'                       = ''
        'Get-WinADGPOMissingPermissions'             = ''
        'Get-WinADGPOSysvolFolders'                  = ''
        'Get-WinADLastBackup'                        = ''
        'Get-WinADLDAPBindingsSummary'               = ''
        'Get-WinADLMSettings'                        = ''
        'Get-WinADObject'                            = ''
        'Get-WinADPrivilegedObjects'                 = 'Get-WinADPriviligedObjects'
        'Get-WinADProxyAddresses'                    = ''
        'Get-WinADSharePermission'                   = ''
        'Get-WinADSiteConnections'                   = ''
        'Get-WinADSiteLinks'                         = ''
        'Get-WinADTomebstoneLifetime'                = 'Get-WinADForestTomebstoneLifetime'
        'Get-WinADTrustLegacy'                       = ''
        'Get-WinADUserPrincipalName'                 = ''
        'Get-WinADUsersForeignSecurityPrincipalList' = 'Get-WinADUsersFP'
        'Get-WinADWellKnownFolders'                  = ''
        'Remove-ADACL'                               = ''
        'Remove-WinADDuplicateObject'                = ''
        'Remove-WinADSharePermission'                = ''
        'Rename-WinADUserPrincipalName'              = ''
        'Repair-WinADEmailAddress'                   = ''
        'Set-ADACLOwner'                             = ''
        'Set-WinADDiagnostics'                       = ''
        'Set-WinADReplication'                       = ''
        'Set-WinADReplicationConnections'            = ''
        'Set-WinADShare'                             = ''
        'Set-WinADTombstoneLifetime'                 = ''
        'Sync-DomainController'                      = ''
        'Test-ADDomainController'                    = ''
        'Test-ADRolesAvailability'                   = ''
        'Test-ADSiteLinks'                           = ''
        'Test-DNSNameServers'                        = ''
        'Test-FSMORolesAvailability'                 = ''
        'Test-LDAP'                                  = ''
    }
    DHCPServer                       = @{'Get-WinDHCP' = '' }
}
[Array] $FunctionsAll = 'Add-ADACL', 'Get-ADACL', 'Get-ADACLOwner', 'Get-DNSServerIP', 'Get-WinADACLConfiguration', 'Get-WinADACLForest', 'Get-WinADBitlockerLapsSummary', 'Get-WinADComputers', 'Get-WinADDelegatedAccounts', 'Get-WinADDFSHealth', 'Get-WinADDHCP', 'Get-WinADDiagnostics', 'Get-WinADDomain', 'Get-WinADDuplicateObject', 'Get-WinADForest', 'Get-WinADForestControllerInformation', 'Get-WinADForestOptionalFeatures', 'Get-WinADForestReplication', 'Get-WinADForestRoles', 'Get-WinADForestSchemaProperties', 'Get-WinADForestSites', 'Get-WinADForestSubnet', 'Get-WinADGroupMember', 'Get-WinADGroupMemberOf', 'Get-WinADLastBackup', 'Get-WinADLDAPBindingsSummary', 'Get-WinADLMSettings', 'Get-WinADObject', 'Get-WinADPrivilegedObjects', 'Get-WinADProxyAddresses', 'Get-WinADServiceAccount', 'Get-WinADSharePermission', 'Get-WinADSiteConnections', 'Get-WinADSiteLinks', 'Get-WinADTomebstoneLifetime', 'Get-WinADTrust', 'Get-WinADTrustLegacy', 'Get-WinADUserPrincipalName', 'Get-WinADUsers', 'Get-WinADUsersForeignSecurityPrincipalList', 'Get-WinADWellKnownFolders', 'Invoke-ADEssentials', 'Remove-ADACL', 'Remove-WinADDuplicateObject', 'Remove-WinADSharePermission', 'Rename-WinADUserPrincipalName', 'Repair-WinADACLConfigurationOwner', 'Repair-WinADEmailAddress', 'Repair-WinADForestControllerInformation', 'Set-ADACLOwner', 'Set-DnsServerIP', 'Set-WinADDiagnostics', 'Set-WinADReplication', 'Set-WinADReplicationConnections', 'Set-WinADShare', 'Set-WinADTombstoneLifetime', 'Show-WinADGroupMember', 'Show-WinADGroupMemberOf', 'Show-WinADTrust', 'Sync-DomainController', 'Test-ADDomainController', 'Test-ADRolesAvailability', 'Test-ADSiteLinks', 'Test-DNSNameServers', 'Test-FSMORolesAvailability', 'Test-LDAP'
[Array] $AliasesAll = 'Get-WinADDomainRoles', 'Get-WinADForestObjectsConflict', 'Get-WinADForestTomebstoneLifetime', 'Get-WinADPriviligedObjects', 'Get-WinADRoles', 'Get-WinADSubnet', 'Get-WinADTrusts', 'Get-WinADUsersFP', 'Get-WinDNSServerIP', 'Set-WinDNSServerIP', 'Show-ADGroupMember', 'Show-ADGroupMemberOf', 'Show-ADTrust', 'Show-ADTrusts', 'Show-WinADTrusts'
$AliasesToRemove = [System.Collections.Generic.List[string]]::new()
$FunctionsToRemove = [System.Collections.Generic.List[string]]::new()
foreach ($Module in $ModuleFunctions.Keys) {
    try { Import-Module -Name $Module -ErrorAction Stop } catch {
        foreach ($Function in $ModuleFunctions[$Module].Keys) {
            $FunctionsToRemove.Add($Function)
            $ModuleFunctions[$Module][$Function] | ForEach-Object { if ($_) { $AliasesToRemove.Add($_) } }
        }
    }
}
$FunctionsToLoad = foreach ($Function in $FunctionsAll) { if ($Function -notin $FunctionsToRemove) { $Function } }
$AliasesToLoad = foreach ($Alias in $AliasesAll) { if ($Alias -notin $AliasesToRemove) { $Alias } }
Export-ModuleMember -Function @($FunctionsToLoad) -Alias @($AliasesToLoad)
# SIG # Begin signature block
# MIIdWQYJKoZIhvcNAQcCoIIdSjCCHUYCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUW/MdX5+NjGQ0B/HJIbM2T1Xr
# vzGgghhnMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B
# AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
# Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg
# +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT
# XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5
# a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g
# 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1
# roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
# GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G
# A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL
# gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3
# cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr
# EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+
# fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q
# Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu
# 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw
# 8jCCBP4wggPmoAMCAQICEA1CSuC+Ooj/YEAhzhQA8N0wDQYJKoZIhvcNAQELBQAw
# cjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVk
# IElEIFRpbWVzdGFtcGluZyBDQTAeFw0yMTAxMDEwMDAwMDBaFw0zMTAxMDYwMDAw
# MDBaMEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4G
# A1UEAxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjEwggEiMA0GCSqGSIb3DQEBAQUA
# A4IBDwAwggEKAoIBAQDC5mGEZ8WK9Q0IpEXKY2tR1zoRQr0KdXVNlLQMULUmEP4d
# yG+RawyW5xpcSO9E5b+bYc0VkWJauP9nC5xj/TZqgfop+N0rcIXeAhjzeG28ffnH
# bQk9vmp2h+mKvfiEXR52yeTGdnY6U9HR01o2j8aj4S8bOrdh1nPsTm0zinxdRS1L
# sVDmQTo3VobckyON91Al6GTm3dOPL1e1hyDrDo4s1SPa9E14RuMDgzEpSlwMMYpK
# jIjF9zBa+RSvFV9sQ0kJ/SYjU/aNY+gaq1uxHTDCm2mCtNv8VlS8H6GHq756Wwog
# L0sJyZWnjbL61mOLTqVyHO6fegFz+BnW/g1JhL0BAgMBAAGjggG4MIIBtDAOBgNV
# HQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDBBBgNVHSAEOjA4MDYGCWCGSAGG/WwHATApMCcGCCsGAQUFBwIBFhtodHRwOi8v
# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHwYDVR0jBBgwFoAU9LbhIB3+Ka7S5GGlsqIl
# ssgXNW4wHQYDVR0OBBYEFDZEho6kurBmvrwoLR1ENt3janq8MHEGA1UdHwRqMGgw
# MqAwoC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMu
# Y3JsMDKgMKAuhixodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVk
# LXRzLmNybDCBhQYIKwYBBQUHAQEEeTB3MCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz
# cC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKGQ2h0dHA6Ly9jYWNlcnRzLmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURUaW1lc3RhbXBpbmdDQS5jcnQw
# DQYJKoZIhvcNAQELBQADggEBAEgc3LXpmiO85xrnIA6OZ0b9QnJRdAojR6OrktIl
# xHBZvhSg5SeBpU0UFRkHefDRBMOG2Tu9/kQCZk3taaQP9rhwz2Lo9VFKeHk2eie3
# 8+dSn5On7UOee+e03UEiifuHokYDTvz0/rdkd2NfI1Jpg4L6GlPtkMyNoRdzDfTz
# ZTlwS/Oc1np72gy8PTLQG8v1Yfx1CAB2vIEO+MDhXM/EEXLnG2RJ2CKadRVC9S0y
# OIHa9GCiurRS+1zgYSQlT7LfySmoc0NR2r1j1h9bm/cuG08THfdKDXF+l7f0P4Tr
# weOjSaH6zqe/Vs+6WXZhiV9+p7SOZ3j5NpjhyyjaW4emii8wggUwMIIEGKADAgEC
# AhAECRgbX9W7ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEw
# MjIxMjAwMDBaFw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV
# BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEi
# MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7
# RZmxOttE9X/lqJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p
# 0WfTxvspJ8fTeyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj
# 6YgsIJWuHEqHCN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grk
# V7tKtel05iv+bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHy
# DxL0xY4PwaLoLFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMB
# AAGjggHNMIIByTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAT
# BgNVHSUEDDAKBggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGG
# GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2Nh
# Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCB
# gQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNl
# cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgG
# CmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
# Y29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1
# DlgwHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEL
# BQADggEBAD7sDVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q
# 3yBVN7Dh9tGSdQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/
# kLEbBw6RFfu6r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dc
# IFzZcbEMj7uo+MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6
# dGRrsutmQ9qzsIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT
# +hKUGIUukpHqaGxEMrJmoecYpJpkUe8wggUxMIIEGaADAgECAhAKoSXW1jIbfkHk
# Bdo2l8IVMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE
# aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMT
# G0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcxMjAwMDBaFw0z
# MTAxMDcxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0
# IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQC90DLuS82Pf92puoKZxTlUKFe2I0rEDgdFM1EQfdD5
# fU1ofue2oPSNs4jkl79jIZCYvxO8V9PD4X4I1moUADj3Lh477sym9jJZ/l9lP+Cb
# 6+NGRwYaVX4LJ37AovWg4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT7l3ImgtU
# 46gJcWvgzyIQD3XPcXJOCq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM/fDqR9mI
# UF79Zm5WYScpiYRR5oLnRlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F0IQZchfx
# FwbvPc3WTe8GQv2iUypPhR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHOMIIByjAd
# BgNVHQ4EFgQU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAUReuir/SS
# y4IxLVGLp6chnfNtyA8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMC
# AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5j
# cnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwUAYDVR0gBEkw
# RzA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj
# ZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4IBAQBxlRLp
# UYdWac3v3dp8qmN6s3jPBjdAhO9LhL/KzwMC/cWnww4gQiyvd/MrHwwhWiq3BTQd
# aq6Z+CeiZr8JqmDfdqQ6kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVDBGiy23UC
# 4HLHmNY8ZOUfSBAYX4k4YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3CzddWThZN+
# tpJn+1Nhiaj1a5bA9FhpDXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UBJrZspe6H
# USHkWGCbugwtK22ixH67xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0e/VWMyIv
# IjayS6JKldj1po5SMIIFPTCCBCWgAwIBAgIQBNXcH0jqydhSALrNmpsqpzANBgkq
# hkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBT
# SEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMDYyNjAwMDAwMFoX
# DTIzMDcwNzEyMDAwMFowejELMAkGA1UEBhMCUEwxEjAQBgNVBAgMCcWabMSFc2tp
# ZTERMA8GA1UEBxMIS2F0b3dpY2UxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlz
# IEVWT1RFQzEhMB8GA1UEAwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7KB3iyBrhkLUbbFe9qxhKKPBYqD
# Bqlnr3AtpZplkiVjpi9dMZCchSeT5ODsShPuZCIxJp5I86uf8ibo3vi2S9F9AlfF
# jVye3dTz/9TmCuGH8JQt13ozf9niHecwKrstDVhVprgxi5v0XxY51c7zgMA2g1Ub
# +3tii0vi/OpmKXdL2keNqJ2neQ5cYly/GsI8CREUEq9SZijbdA8VrRF3SoDdsWGf
# 3tZZzO6nWn3TLYKQ5/bw5U445u/V80QSoykszHRivTj+H4s8ABiforhi0i76beA6
# Ea41zcH4zJuAp48B4UhjgRDNuq8IzLWK4dlvqrqCBHKqsnrF6BmBrv+BXQIDAQAB
# o4IBxTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0O
# BBYEFBixNSfoHFAgJk4JkDQLFLRNlJRmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE
# DDAKBggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2Ny
# bDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUw
# QzA3BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNl
# cnQuY29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8v
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNp
# Z25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAmr1s
# z4lsLARi4wG1eg0B8fVJFowtect7SnJUrp6XRnUG0/GI1wXiLIeow1UPiI6uDMsR
# XPHUF/+xjJw8SfIbwava2eXu7UoZKNh6dfgshcJmo0QNAJ5PIyy02/3fXjbUREHI
# NrTCvPVbPmV6kx4Kpd7KJrCo7ED18H/XTqWJHXa8va3MYLrbJetXpaEPpb6zk+l8
# Rj9yG4jBVRhenUBUUj3CLaWDSBpOA/+sx8/XB9W9opYfYGb+1TmbCkhUg7TB3gD6
# o6ESJre+fcnZnPVAPESmstwsT17caZ0bn7zETKlNHbc1q+Em9kyBjaQRcEQoQQNp
# ezQug9ufqExx6lHYDjGCBFwwggRYAgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYD
# VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAv
# BgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EC
# EATV3B9I6snYUgC6zZqbKqcwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAI
# oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB
# CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFA8b6UGewpAr9OK+hMMD
# gayf6rcTMA0GCSqGSIb3DQEBAQUABIIBAAuB9Rd6Bc5meoxxPMVYdidwWI29AzUl
# u3dfyOkwDToYXh6JajqhXuo1iGKQwjdRlLOmAP97DtBaEiL6pDsbPbGUXdltbXUN
# v44OmH9DtOGqeTLV3iq1xWlTTNAUFozF+x5xxnahXz1ia4jOylmgUVvuadVQP1L0
# msl4iWatmUbb1l17o32mLqd0dI5wTKp+Qb1h8vnmnNZWlPwPCDWCXlhBetrJa8bt
# Y1E1Rw/tkR4NJW43itBUMtGz6ACzZ5lV19IU8q9zZY6D6HNdOPCC+ZQw6iiDCsE3
# PmvNf4VJNXNctQP+TeXs9+WHR43s+A36X6qeL50ca8U4eyoeZzCRLA+hggIwMIIC
# LAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8G
# A1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQQIQ
# DUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzEL
# BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIxMDUyNzIwMzEyMVowLwYJKoZI
# hvcNAQkEMSIEIBA141hEUPyLQXHT1Ggv3+OAhrkieVcpUj+9TpK0pGkrMA0GCSqG
# SIb3DQEBAQUABIIBACQ7Yzl0yxNcp9ho2w52+9NuLsE/3F9XvNo4jX1W5KGhNOL+
# qv+eO06Esjc4wlgwjq0C4hh5cxQV95IrxQJYf9MeioliXLsOKkK+rb3oNr85ao79
# 4lT18cObdA106gvzyt44r8ag6FGpmFs0tGawyGiCZ3qyJtDYsDOOhAo+/8f5W29R
# fTzaq1YPV6Ffp7Q+TCfvGyp6pv3nC6KP2q9fSB6AVGlZv+GK4Thm9fyKgtj2/Jwu
# Q/+F87MOmzf3njok24Y6Gd86fNJmU3CiOHkgQ3tOBsRqh016fbcGgkcgCN03JFBX
# Cc1qQY3Gh0xnTo25wg0yY876RCbOiEl+pLlQ7w4=
# SIG # End signature block