ADEssentials.psm1

function ConvertFrom-DistinguishedName {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER DistinguishedName
    Parameter description
 
    .PARAMETER ToOrganizationalUnit
    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([string[]] $DistinguishedName,
        [switch] $ToOrganizationalUnit,
        [switch] $ToDC)
    if ($ToOrganizationalUnit) { return [Regex]::Match($DistinguishedName, '(?=OU)(.*\n?)(?<=.)').Value } elseif ($ToDC) { return [Regex]::Match($DistinguishedName, '(?=DC)(.*\n?)(?<=.)').Value } else {
        $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$'
        $Output = foreach ($_ in $DistinguishedName) {
            $_ -match $Regex
            $Matches
        }
        $Output.cn
    }
}
function ConvertFrom-SID {
    [cmdletbinding()]
    param([string[]] $SID)
    $wellKnownSIDs = @{'S-1-0' = 'Null Authority'
        'S-1-0-0'              = 'Nobody'
        'S-1-1'                = 'World Authority'
        'S-1-1-0'              = 'Everyone'
        'S-1-2'                = 'Local Authority'
        'S-1-2-0'              = 'Local'
        'S-1-2-1'              = 'Console Logon'
        'S-1-3'                = 'Creator Authority'
        'S-1-3-0'              = 'Creator Owner'
        'S-1-3-1'              = 'Creator Group'
        'S-1-3-2'              = 'Creator Owner Server'
        'S-1-3-3'              = 'Creator Group Server'
        'S-1-3-4'              = 'Owner Rights'
        'S-1-5-80-0'           = 'All Services'
        'S-1-4'                = 'Non-unique Authority'
        'S-1-5'                = 'NT Authority'
        'S-1-5-1'              = 'Dialup'
        'S-1-5-2'              = 'Network'
        'S-1-5-3'              = 'Batch'
        'S-1-5-4'              = 'Interactive'
        'S-1-5-6'              = 'Service'
        'S-1-5-7'              = 'Anonymous'
        'S-1-5-8'              = 'Proxy'
        'S-1-5-9'              = 'Enterprise Domain Controllers'
        'S-1-5-10'             = 'Principal Self'
        'S-1-5-11'             = 'Authenticated Users'
        'S-1-5-12'             = 'Restricted Code'
        'S-1-5-13'             = 'Terminal Server Users'
        'S-1-5-14'             = 'Remote Interactive Logon'
        'S-1-5-15'             = 'This Organization'
        'S-1-5-17'             = 'This Organization'
        'S-1-5-18'             = 'Local System'
        'S-1-5-19'             = 'NT Authority'
        'S-1-5-20'             = 'NT Authority'
        'S-1-5-32-544'         = 'Administrators'
        'S-1-5-32-545'         = 'Users'
        'S-1-5-32-546'         = 'Guests'
        'S-1-5-32-547'         = 'Power Users'
        'S-1-5-32-548'         = 'Account Operators'
        'S-1-5-32-549'         = 'Server Operators'
        'S-1-5-32-550'         = 'Print Operators'
        'S-1-5-32-551'         = 'Backup Operators'
        'S-1-5-32-552'         = 'Replicators'
        'S-1-5-64-10'          = 'NTLM Authentication'
        'S-1-5-64-14'          = 'SChannel Authentication'
        'S-1-5-64-21'          = 'Digest Authority'
        'S-1-5-80'             = 'NT Service'
        'S-1-5-83-0'           = 'NT VIRTUAL MACHINE\Virtual Machines'
        'S-1-16-0'             = 'Untrusted Mandatory Level'
        'S-1-16-4096'          = 'Low Mandatory Level'
        'S-1-16-8192'          = 'Medium Mandatory Level'
        'S-1-16-8448'          = 'Medium Plus Mandatory Level'
        'S-1-16-12288'         = 'High Mandatory Level'
        'S-1-16-16384'         = 'System Mandatory Level'
        'S-1-16-20480'         = 'Protected Process Mandatory Level'
        'S-1-16-28672'         = 'Secure Process Mandatory Level'
        'S-1-5-32-554'         = 'BUILTIN\Pre-Windows 2000 Compatible Access'
        'S-1-5-32-555'         = 'BUILTIN\Remote Desktop Users'
        'S-1-5-32-556'         = 'BUILTIN\Network Configuration Operators'
        'S-1-5-32-557'         = 'BUILTIN\Incoming Forest Trust Builders'
        'S-1-5-32-558'         = 'BUILTIN\Performance Monitor Users'
        'S-1-5-32-559'         = 'BUILTIN\Performance Log Users'
        'S-1-5-32-560'         = 'BUILTIN\Windows Authorization Access Group'
        'S-1-5-32-561'         = 'BUILTIN\Terminal Server License Servers'
        'S-1-5-32-562'         = 'BUILTIN\Distributed COM Users'
        'S-1-5-32-569'         = 'BUILTIN\Cryptographic Operators'
        'S-1-5-32-573'         = 'BUILTIN\Event Log Readers'
        'S-1-5-32-574'         = 'BUILTIN\Certificate Service DCOM Access'
        'S-1-5-32-575'         = 'BUILTIN\RDS Remote Access Servers'
        'S-1-5-32-576'         = 'BUILTIN\RDS Endpoint Servers'
        'S-1-5-32-577'         = 'BUILTIN\RDS Management Servers'
        'S-1-5-32-578'         = 'BUILTIN\Hyper-V Administrators'
        'S-1-5-32-579'         = 'BUILTIN\Access Control Assistance Operators'
        'S-1-5-32-580'         = 'BUILTIN\Remote Management Users'
    }
    foreach ($_ in $SID) {
        if ($wellKnownSIDs[$_]) {
            [PSCustomObject] @{Name = $wellKnownSIDs[$_]
                SID                 = $_
            }
        } else {
            try {
                [PSCustomObject] @{Name = (([System.Security.Principal.SecurityIdentifier]::new($_)).Translate([System.Security.Principal.NTAccount])).Value
                    SID                 = $_
                }
            } catch {
                [PSCustomObject] @{Name = $_
                    SID                 = $_
                }
            }
        }
    }
}
function Convert-TimeToDays {
    [CmdletBinding()]
    param ($StartTime,
        $EndTime,
        [string] $Ignore = '*1601*')
    if ($null -ne $StartTime -and $null -ne $EndTime) { try { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start $StartTime -End $EndTime).Days } } catch { } } elseif ($null -ne $EndTime) { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start (Get-Date) -End ($EndTime)).Days } } elseif ($null -ne $StartTime) { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start $StartTime -End (Get-Date)).Days } }
    return $Days
}
function Convert-ToDateTime {
    [CmdletBinding()]
    param ([string] $Timestring,
        [string] $Ignore = '*1601*')
    Try { $DateTime = ([datetime]::FromFileTime($Timestring)) } catch { $DateTime = $null }
    if ($null -eq $DateTime -or $DateTime -like $Ignore) { return $null } else { return $DateTime }
}
function ConvertTo-OperatingSystem {
    [CmdletBinding()]
    param([string] $OperatingSystem,
        [string] $OperatingSystemVersion)
    if ($OperatingSystem -like '*Windows 10*') {
        $Systems = @{'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.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 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" }
            if ([int32]$V -band 0x00000004) { "Quarantaine (SID Filtering enabled)" }
            if ([int32]$V -band 0x00000008) { "Forest Transitive" }
            if ([int32]$V -band 0x00000010) { "Cross Organization (Selective Authentication enabled)" }
            if ([int32]$V -band 0x00000020) { "Within Forest" }
            if ([int32]$V -band 0x00000040) { "Treat as External" }
            if ([int32]$V -band 0x00000080) { "Uses RC4 Encryption" }
        })
    return $TrustAttributes
}
function Get-CimData {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER ComputerName
    Parameter description
 
    .PARAMETER Protocol
    Parameter description
 
    .PARAMETER Class
    Parameter description
 
    .PARAMETER Properties
    Parameter description
 
    .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([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'
    try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Env:COMPUTERNAME }
    $CimObject = @(# requires removal of this property for query
        [string[]] $PropertiesOnly = $Properties | Where-Object { $_ -ne 'PSComputerName' }
        $Computers = $ComputerName | Where-Object { $_ -ne $Env:COMPUTERNAME -and $_ -ne $LocalComputerDNSName }
        if ($Computers.Count -gt 0) {
            if ($Protocol = 'Default') { Get-CimInstance -ClassName $Class -ComputerName $Computers -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties } else {
                $Option = New-CimSessionOption -Protocol
                $Session = New-CimSession -ComputerName $Computers -SessionOption $Option -ErrorAction SilentlyContinue
                $Info = Get-CimInstance -ClassName $Class -CimSession $Session -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
                $null = Remove-CimSession -CimSession $Session -ErrorAction SilentlyContinue
                $Info
            }
        }
        $Computers = $ComputerName | Where-Object { $_ -eq $Env:COMPUTERNAME -or $_ -eq $LocalComputerDNSName }
        if ($Computers.Count -gt 0) {
            $Info = Get-CimInstance -ClassName $Class -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
            $Info | Add-Member -Name 'PSComputerName' -Value $Computers -MemberType NoteProperty -Force
            $Info
        })
    $CimComputers = $CimObject.PSComputerName | Sort-Object -Unique
    foreach ($Computer in $ComputerName) { if ($CimComputers -notcontains $Computer) { Write-Warning "Get-CimData - No data for computer $Computer. Most likely an error on receiving side." } }
    return $CimObject
}
function Get-PSRegistry {
    [cmdletbinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME,
        [string[]] $RegistryPath,
        [string] $Value)
    $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'                  = 'GetExpandedStringValue'
        '11'                 = 'GetQWORDValue'
    }
    [uint32] $RootKey = $null
    [Array] $Computers = $ComputerName.Where( { $_ -ne $Env:COMPUTERNAME }, 'Split')
    foreach ($Registry in $RegistryPath) {
        for ($ComputerSplit = 0; $ComputerSplit -lt $Computers.Count; $ComputerSplit++) {
            if ($Computers[$ComputerSplit].Count -gt 0) {
                $Arguments = foreach ($_ in $RootKeyDictionary.Keys) {
                    if ($Registry.StartsWith($_)) {
                        $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 -ComputerName $Computers[$ComputerSplit] -Arguments $Arguments } else { $Output2 = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumValues -Arguments $Arguments }
                foreach ($Entry in $Output2) {
                    $RegistryOutput = [ordered] @{ }
                    $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 -ComputerName $Entry.PSComputerName } else { $Values = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments }
                        if ($null -ne $Values.sValue) { $RegistryOutput[$Names[$i]] = $Values.sValue } elseif ($null -ne $Values.uValue) { $RegistryOutput[$Names[$i]] = $Values.uValue }
                    }
                    if ($ComputerSplit -eq 0) { $RegistryOutput['ComputerName'] = $Entry.PSComputerName } else { $RegistryOutput['ComputerName'] = $ENV:COMPUTERNAME }
                    [PSCustomObject] $RegistryOutput
                }
            }
        }
    }
}
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)
    if ($Global:ProgressPreference -ne 'SilentlyContinue') {
        $TemporaryProgress = $Global:ProgressPreference
        $Global:ProgressPreference = 'SilentlyContinue'
    }
    $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['QueryServers']['Forest'] = $DC
    $Findings.Domains = foreach ($_ in $ForestInformation.Domains) {
        if ($IncludeDomains) {
            if ($_ -in $IncludeDomains) { $_.ToLower() }
            continue
        }
        if ($_ -notin $ExcludeDomains) { $_.ToLower() }
    }
    $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) {
        try { $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop } catch {
            Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)"
            continue
        }
        $Findings['QueryServers']["$Domain"] = $DC
        [Array] $AllDC = try {
            try { $DomainControllers = Get-ADDomainController -Filter $Filter -Server $DC.HostName[0] -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 -notin $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) { $Findings[$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { $Findings[$Domain] = $AllDC }
        $Findings[$Domain]
    }
    if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress }
    $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 Add-ToHashTable {
    param($Hashtable, $Key, $Value)
    if ($null -ne $Value -and $Value -ne '') { $Hashtable.Add($Key, $Value) }
}
function New-Runspace {
    [cmdletbinding()]
    param ([int] $minRunspaces = 1,
        [int] $maxRunspaces = [int]$env:NUMBER_OF_PROCESSORS + 1)
    $RunspacePool = [RunspaceFactory]::CreateRunspacePool($minRunspaces, $maxRunspaces)
    $RunspacePool.Open()
    return $RunspacePool
}
function Split-Array {
    [CmdletBinding()]
    <#
        .SYNOPSIS
        Split an array
        .NOTES
        Version : July 2, 2017 - implemented suggestions from ShadowSHarmon for performance
        .PARAMETER inArray
        A one dimensional array you want to split
        .EXAMPLE
        This splits array into multiple arrays of 3
        Example below wil return 1,2,3 + 4,5,6 + 7,8,9
 
        Split-array -inArray @(1,2,3,4,5,6,7,8,9,10) -parts 3
        .EXAMPLE
        This splits array into 3 parts regardless of amount of elements
 
 
        Split-array -inArray @(1,2,3,4,5,6,7,8,9,10) -size 3
 
        # Link: https://gallery.technet.microsoft.com/scriptcenter/Split-an-array-into-parts-4357dcc1
    #>

    param([Object] $inArray,
        [int]$parts,
        [int]$size)
    if ($inArray.Count -eq 1) { return $inArray }
    if ($parts) { $PartSize = [Math]::Ceiling($inArray.count / $parts) }
    if ($size) {
        $PartSize = $size
        $parts = [Math]::Ceiling($inArray.count / $size)
    }
    $outArray = New-Object 'System.Collections.Generic.List[psobject]'
    for ($i = 1; $i -le $parts; $i++) {
        $start = (($i - 1) * $PartSize)
        $end = (($i) * $PartSize) - 1
        if ($end -ge $inArray.count) { $end = $inArray.count - 1 }
        $outArray.Add(@($inArray[$start..$end]))
    }
    return , $outArray
}
function Start-Runspace {
    [cmdletbinding()]
    param ([ScriptBlock] $ScriptBlock,
        [System.Collections.IDictionary] $Parameters,
        [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool)
    if ($ScriptBlock -ne '') {
        $runspace = [PowerShell]::Create()
        $null = $runspace.AddScript($ScriptBlock)
        if ($null -ne $Parameters) { $null = $runspace.AddParameters($Parameters) }
        $runspace.RunspacePool = $RunspacePool
        [PSCustomObject]@{Pipe = $runspace
            Status             = $runspace.BeginInvoke()
        }
    }
}
function Stop-Runspace {
    [cmdletbinding()]
    param([Array] $Runspaces,
        [string] $FunctionName,
        [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool,
        [switch] $ExtendedOutput)
    [Array] $List = While (@($Runspaces | Where-Object -FilterScript { $null -ne $_.Status }).count -gt 0) {
        foreach ($Runspace in $Runspaces | Where-Object { $_.Status.IsCompleted -eq $true }) {
            $Errors = foreach ($e in $($Runspace.Pipe.Streams.Error)) {
                Write-Error -ErrorRecord $e
                $e
            }
            foreach ($w in $($Runspace.Pipe.Streams.Warning)) { Write-Warning -Message $w }
            foreach ($v in $($Runspace.Pipe.Streams.Verbose)) { Write-Verbose -Message $v }
            if ($ExtendedOutput) {
                @{Output   = $Runspace.Pipe.EndInvoke($Runspace.Status)
                    Errors = $Errors
                }
            } else { $Runspace.Pipe.EndInvoke($Runspace.Status) }
            $Runspace.Status = $null
        }
    }
    $RunspacePool.Close()
    $RunspacePool.Dispose()
    if ($List.Count -eq 1) { return , $List } else { return $List }
}
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
}
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
            }
        }
    }
}
$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
                        Write-Verbose "New-ADForestDrives - Mapped drive $Domain / $($Server.Hostname[0])"
                    } else { $null = New-PSDrive -Name $DNConverted -Root '' -PsProvider ActiveDirectory -Server $Domain -Scope Global }
                } 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
                    $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 $Object -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
                            Write-Verbose "New-ADForestDrives - Mapped drive $Domain / $Server"
                        } else { $null = New-PSDrive -Name $DNConverted -Root '' -PsProvider ActiveDirectory -Server $Domain -Scope Global }
                    } catch { Write-Warning "New-ADForestDrives - Couldn't map new AD psdrive for $Domain / $Server $($_.Exception.Message)" }
                }
            }
        }
        $Global:ADDrivesMapped = $true
    }
}
function Test-LDAPPorts {
    [CmdletBinding()]
    param([string] $ServerName,
        [int] $Port)
    if ($ServerName -and $Port -ne 0) {
        try {
            $LDAP = "LDAP://" + $ServerName + ':' + $Port
            $Connection = [ADSI]($LDAP)
            $Connection.Close()
            return $true
        } catch { if ($_.Exception.ToString() -match "The server is not operational") { Write-Warning "Can't open $ServerName`:$Port." } elseif ($_.Exception.ToString() -match "The user name or password is incorrect") { Write-Warning "Current user ($Env:USERNAME) doesn't seem to have access to to LDAP on port $Server`:$Port" } else { Write-Warning -Message $_ } }
        return $False
    }
}
function Get-ADACL {
    [cmdletbinding()]
    param([Parameter(ValueFromPipeline)][Array] $ADObject,
        [string] $Domain = $Env:USERDNSDOMAIN,
        [Object] $Server,
        [string] $ForestName,
        [switch] $Extended)
    Begin {
        if (-not $Script:ForestGUIDs) {
            Write-Verbose "Get-ADACL - Gathering Forest GUIDS"
            $Script:ForestGUIDs = Get-WinADForestGUIDs
        }
        if (-not $Script:ForestCache) {
            Write-Verbose "Get-ADACL - Building Cache"
            $Script:ForestCache = Get-WinADCache -ByNetBiosName
        }
    }
    Process {
        foreach ($Object in $ADObject) {
            if ($Object -is [string]) { } else { }
            Write-Verbose "Get-ADACL - Enabling PSDrives"
            New-ADForestDrives -ForestName $ForestName
            $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $Object -ToDC) -replace '=' -replace ','
            Write-Verbose "Get-ADACL - Getting ACL from $Object"
            if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                Write-Warning "Get-ADACL - Drive $DNConverted not mapped. Terminating..."
                return
            }
            $ACLs = Get-Acl -Path "$DNConverted`:\$($Object)" | Select-Object -ExpandProperty Access
            foreach ($ACL in $ACLs) {
                if ($ACL.IdentityReference -like '*\*') {
                    if ($Script:ForestCache) {
                        $TemporaryIdentity = $Script:ForestCache["$($ACL.IdentityReference)"]
                        $IdentityReferenceType = $TemporaryIdentity.ObjectClass
                        $IdentityReference = $ACL.IdentityReference.Value
                    } else {
                        $IdentityReferenceType = ''
                        $IdentityReference = $ACL.IdentityReference.Value
                    }
                } elseif ($ACL.IdentityReference -like '*-*-*-*') {
                    $ConvertedSID = ConvertFrom-SID -sid $ACL.IdentityReference
                    if ($Script:ForestCache) {
                        $TemporaryIdentity = $Script:ForestCache["$($ConvertedSID.Name)"]
                        $IdentityReferenceType = $TemporaryIdentity.ObjectClass
                    } else { $IdentityReferenceType = '' }
                    $IdentityReference = $ConvertedSID.Name
                } else {
                    $IdentityReference = $ACL.IdentityReference
                    $IdentityReferenceType = 'Unknown'
                }
                $ReturnObject = [ordered] @{'DistinguishedName' = $Object
                    'AccessControlType'                         = $ACL.AccessControlType
                    'Rights'                                    = $Rights
                    'Principal'                                 = $IdentityReference
                    'PrincipalType'                             = $IdentityReferenceType
                    'ObjectTypeName'                            = $Script:ForestGUIDs["$($ACL.objectType)"]
                    'InheritedObjectTypeName'                   = $Script:ForestGUIDs["$($ACL.inheritedObjectType)"]
                    'ActiveDirectoryRights'                     = $ACL.ActiveDirectoryRights
                    'InheritanceType'                           = $ACL.InheritanceType
                    '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
                }
                [PSCustomObject] $ReturnObject
            }
        }
    }
    End { }
}
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)
    $Today = Get-Date
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    $ComputerProperties = @(if ($Forest) {
            $Type = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest
            $Context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new($Type, $ForestInformation.Forest)
            $Schema = [directoryservices.activedirectory.activedirectoryschema]::GetSchema($Context)
        } else { $Schema = [directoryservices.activedirectory.activedirectoryschema]::GetCurrentSchema() }
        @($Schema.FindClass("computer").mandatoryproperties | Select-Object name, commonname, description, syntax
            $Schema.FindClass("computer").optionalproperties | Select-Object name, commonname, description, syntax))
    if ($ComputerProperties.Name -contains 'ms-Mcs-AdmPwd') {
        $LapsAvailable = $true
        $Properties = @('Name'
            'OperatingSystem'
            'OperatingSystemVersion'
            'DistinguishedName'
            'LastLogonDate'
            'PasswordLastSet'
            'ms-Mcs-AdmPwd'
            'ms-Mcs-AdmPwdExpirationTime')
    } else {
        $LapsAvailable = $false
        $Properties = @('Name'
            'OperatingSystem'
            'OperatingSystemVersion'
            'DistinguishedName'
            'LastLogonDate'
            'PasswordLastSet')
    }
    $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
                    DistinguishedName       = $_.DistinguishedName
                    System                  = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
                    LastLogonDate           = $_.LastLogonDate
                    LastLogonDays           = $LastLogonDays
                    PasswordLastSet         = $_.PasswordLastSet
                    PasswordLastChangedDays = $PasswordLastChangedDays
                    Laps                    = $Laps
                    LapsExpirationDays      = $LapsExpirationDays
                    LapsExpirationTime      = $LapsExpirationTime
                    OrganizationalUnit      = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit
                }
            } elseif ($BitlockerOnly) {
                [PSCustomObject] @{Name     = $_.Name
                    Enabled                 = $_.Enabled
                    Domain                  = $Domain
                    DNSHostName             = $_.DNSHostName
                    DistinguishedName       = $_.DistinguishedName
                    System                  = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
                    LastLogonDate           = $_.LastLogonDate
                    LastLogonDays           = $LastLogonDays
                    PasswordLastSet         = $_.PasswordLastSet
                    PasswordLastChangedDays = $PasswordLastChangedDays
                    Encrypted               = $Encrypted
                    EncryptedTime           = $EncryptedTime
                    OrganizationalUnit      = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit
                }
            } else {
                [PSCustomObject] @{Name     = $_.Name
                    Enabled                 = $_.Enabled
                    Domain                  = $Domain
                    DNSHostName             = $_.DNSHostName
                    DistinguishedName       = $_.DistinguishedName
                    System                  = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
                    LastLogonDate           = $_.LastLogonDate
                    LastLogonDays           = $LastLogonDays
                    PasswordLastSet         = $_.PasswordLastSet
                    PasswordLastChangedDays = $PasswordLastChangedDays
                    Encrypted               = $Encrypted
                    EncryptedTime           = $EncryptedTime
                    Laps                    = $Laps
                    LapsExpirationDays      = $LapsExpirationDays
                    LapsExpirationTime      = $LapsExpirationTime
                    OrganizationalUnit      = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit
                }
            }
        }
    }
    $FormattedComputers
}
function Get-WinADDFSHealth {
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [int] $EventDays = 1)
    $Today = (Get-Date)
    $Yesterday = (Get-Date -Hour 0 -Second 0 -Minute 0 -Millisecond 0).AddDays(-$EventDays)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC
    [Array] $Table = foreach ($Domain in $ForestInformation.Domains) {
        Write-Verbose "Get-WinADDFSHealth - Processing $Domain"
        $DomainControllersFull = $ForestInformation["$Domain"]
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        try { [Array]$GPOs = @(Get-GPO -All -Domain $Domain -Server $QueryServer) } 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 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.IsPDC
                '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
            }
            $WarningVar = $null
            $DFSReplicatedFolderInfo = Get-CimData -NameSpace "root\microsoftdfs" -Class 'dfsrreplicatedfolderinfo' -ComputerName $Hostname -WarningAction SilentlyContinue -WarningVariable WarningVar
            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 $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 $LocalSettings -Server $QueryServer -ErrorAction Stop
                $DomainSummary['DFSLocalSetting'] = if ($DFSLocalSetting) { $true } else { $false }
            } catch { $DomainSummary['DFSLocalSetting'] = $false }
            try {
                $DomainSystemVolume = Get-ADObject $Subscriber -Server $QueryServer -ErrorAction Stop
                $DomainSummary['DomainSystemVolume'] = if ($DomainSystemVolume) { $true } else { $false }
            } catch { $DomainSummary['DomainSystemVolume'] = $false }
            try {
                $SysVolSubscription = Get-ADObject $Subscription -Server $QueryServer -ErrorAction Stop
                $DomainSummary['SYSVOLSubscription'] = if ($SysVolSubscription) { $true } else { $false }
            } catch { $DomainSummary['SYSVOLSubscription'] = $false }
            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 }
            $All = @($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-WinADForestObjectsConflict {
    [CmdletBinding()]
    Param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    foreach ($Domain in $ForestInformation.Domains) {
        $DC = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        Get-ADObject -LDAPFilter "(|(cn=*\0ACNF:*)(ou=*CNF:*))" -Properties WhenChanged -Server $DC |
            ForEach-Object { $LiveObject = $null
                $ConflictObject = [PSCustomObject] @{Name = $_
                    ConflictDn                            = $_.DistinguishedName
                    ConflictWhenChanged                   = $_.WhenChanged
                    LiveDn                                = "N/A"
                    LiveWhenChanged                       = "N/A"
                }
                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
                    }
                }
                $ConflictObject }
    }
}
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)
    $ProcessErrors = [System.Collections.Generic.List[PSCustomObject]]::new()
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC
    $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 = ', ')
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC
    $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-WinADGPOMissingPermissions {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Domain
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    Based on https://secureinfra.blog/2018/12/31/most-common-mistakes-in-active-directory-and-domain-services-part-1/
    #>

    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [switch] $SkipRODC)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $GPOs = Get-GPO -All -Domain $Domain -Server $QueryServer
        $MissingPermissions = @(foreach ($GPO in $GPOs) {
                If ($GPO.User.Enabled) {
                    $GPOPermissionForAuthUsers = Get-GPPermission -Guid $GPO.Id -All -Server $QueryServer | Select-Object -ExpandProperty Trustee | Where-Object { $_.Name -eq "Authenticated Users" }
                    $GPOPermissionForDomainComputers = Get-GPPermission -Guid $GPO.Id -All -Server $QueryServer | Select-Object -ExpandProperty Trustee | Where-Object { $_.Name -eq "Domain Computers" }
                    If (-not $GPOPermissionForAuthUsers -and -not $GPOPermissionForDomainComputers) { $GPO }
                }
            })
        $MissingPermissions
    }
}
function Get-WinADGPOSysvolFolders {
    [alias('Get-WinADGPOSysvol')]
    [cmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [Array] $GPOs)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC
    foreach ($Domain in $ForestInformation.Domains) {
        Write-Verbose "Get-WinADGPOSysvolFolders - Processing $Domain"
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        [Array]$GPOs = @(Get-GPO -All -Domain $Domain -Server $QueryServer)
        foreach ($Server in $ForestInformation["$Domain"]) {
            Write-Verbose "Get-WinADGPOSysvolFolders - Processing $Domain \ $($Server.Hostname)"
            $Differences = @{ }
            $SysvolHash = @{ }
            $GPOGUIDS = $GPOs.ID.GUID
            try { $SYSVOL = Get-ChildItem -Path "\\$($Server.Hostname)\SYSVOL\$Domain\Policies" -ErrorAction Stop } catch { $Sysvol = $Null }
            foreach ($_ in $SYSVOL) {
                $GUID = $_.Name -replace '{' -replace '}'
                $SysvolHash[$GUID] = $_
            }
            $Files = $SYSVOL.Name -replace '{' -replace '}'
            if ($Files) {
                $Comparing = Compare-Object -ReferenceObject $GPOGUIDS -DifferenceObject $Files -IncludeEqual
                foreach ($_ in $Comparing) {
                    if ($_.SideIndicator -eq '==') { $Found = 'Exists' } elseif ($_.SideIndicator -eq '<=') { $Found = 'Not available on SYSVOL' } elseif ($_.SideIndicator -eq '=>') { $Found = 'Orphaned GPO' } else { $Found = 'Orphaned GPO' }
                    $Differences[$_.InputObject] = $Found
                }
            }
            $GPOSummary = @(foreach ($GPO in $GPOS) {
                    if ($null -ne $SysvolHash[$GPO.Id.GUID].FullName) { $ACL = Get-Acl -Path $SysvolHash[$GPO.Id.GUID].FullName } else { $ACL = $null }
                    if ($null -eq $Differences[$GPO.Id.Guid]) { $SysVolStatus = 'Not available on SYSVOL' } else { $SysVolStatus = $Differences[$GPO.Id.Guid] }
                    [PSCustomObject] @{DisplayName = $GPO.DisplayName
                        Status                     = $Differences[$GPO.Id.Guid]
                        DomainName                 = $GPO.DomainName
                        SysvolServer               = $Server.HostName
                        SysvolStatus               = $SysVolStatus
                        Owner                      = $GPO.Owner
                        FileOwner                  = $ACL.Owner
                        Id                         = $GPO.Id.Guid
                        GpoStatus                  = $GPO.GpoStatus
                        Description                = $GPO.Description
                        CreationTime               = $GPO.CreationTime
                        ModificationTime           = $GPO.ModificationTime
                        UserVersion                = $GPO.UserVersion
                        ComputerVersion            = $GPO.ComputerVersion
                        WmiFilter                  = $GPO.WmiFilter
                    }
                }
                foreach ($_ in $Differences.Keys) {
                    if ($Differences[$_] -eq 'Orphaned GPO') {
                        if ($SysvolHash[$_].BaseName -notcontains 'PolicyDefinitions') {
                            if ($null -ne $SysvolHash[$_].FullName) { $ACL = Get-Acl -Path $SysvolHash[$_].FullName } else { $ACL = $null }
                            [PSCustomObject] @{DisplayName = $SysvolHash[$_].BaseName
                                Status                     = 'Orphaned GPO'
                                DomainName                 = $Domain
                                SysvolServer               = $Server.HostName
                                SysvolStatus               = $Differences[$GPO.Id.Guid]
                                Owner                      = $ACL.Owner
                                FileOwner                  = $ACL.Owner
                                Id                         = $_
                                GpoStatus                  = 'Orphaned'
                                Description                = $null
                                CreationTime               = $SysvolHash[$_].CreationTime
                                ModificationTime           = $SysvolHash[$_].LastWriteTime
                                UserVersion                = $null
                                ComputerVersion            = $null
                                WmiFilter                  = $null
                            }
                        }
                    }
                })
            $GPOSummary | Sort-Object -Property DisplayName
        }
    }
}
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)
    $NameUsed = [System.Collections.Generic.List[string]]::new()
    [DateTime] $CurrentDate = Get-Date
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    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-WinADLMSettings {
    [CmdletBinding()]
    param([string] $DomainController)
    $LSA = Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Control\Lsa' -ComputerName $DomainController
    if ($Lsa) {
        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] @{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
        }
    }
}
Function 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)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    $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 | Select-Object @{name = 'Domain'; expression = { $domain } }, distinguishedname
    }
    $AdminCountAll = foreach ($object in $UsersWithAdminCount) {
        $DistinguishedName = $object.distinguishedname
        $IsMember = foreach ($Group in $CriticalGroups) {
            $QueryServer = $ForestInformation['QueryServers']["$($Group.Domain)"].HostName[0]
            $Group = Get-ADGroup -Filter "Member -RecursiveMatch '$DistinguishedName'" -searchbase $Group.DistinguishedName -server $QueryServer
            if ($Group) { $Group.DistinguishedName }
        }
        if ($IsMember.Count -gt 0) { $GroupDomains = $IsMember } else { $GroupDomains = $null }
        if ($Formatted) {
            $GroupDomains = $GroupDomains -join $Splitter
            $User = [PSCustomObject] @{DistinguishedName = $Object.DistinguishedName
                Domain                                   = $Object.domain
                IsOrphaned                               = (-not $Object.isCriticalSystemObject) -and (-not $IsMember -and -not $Object.isCriticalSystemObject)
                IsMember                                 = $IsMember.Count -gt 0
                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'                               = (-not $Object.isCriticalSystemObject) -and (-not $IsMember -and -not $Object.isCriticalSystemObject)
                'IsMember'                                 = $IsMember.Count -gt 0
                '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 {
    [CmdletBinding()]
    param([Microsoft.ActiveDirectory.Management.ADAccount] $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()
    }
    foreach ($_ in $ADUser.ProxyAddresses) {
        $Proxy = $_
        if ($_.StartsWith('SMTP:')) {
            if ($RemovePrefix) { $Proxy = $Proxy -replace 'SMTP:', '' }
            if ($ToLower) { $Proxy = $Proxy.ToLower() }
            $Summary.Primary.Add($Proxy)
        } elseif ($_.StartsWith('smtp:')) {
            if ($RemovePrefix) { $Proxy = $Proxy -replace 'SMTP:', '' }
            if ($ToLower) { $Proxy = $Proxy.ToLower() }
            $Summary.Secondary.Add($Proxy)
        } elseif ($_.StartsWith('x500')) {
            if ($RemovePrefix) { $Proxy = $Proxy -replace 'SMTP:', '' }
            if ($ToLower) { $Proxy = $Proxy.ToLower() }
            $Summary.x500.Add($Proxy)
        } elseif ($_.StartsWith('sip:')) {
            if ($RemovePrefix) { $Proxy = $Proxy -replace 'SMTP:', '' }
            if ($ToLower) { $Proxy = $Proxy.ToLower() }
            $Summary.Sip.Add($Proxy)
        } else {
            if ($RemovePrefix) { $Proxy = $Proxy -replace 'SMTP:', '' }
            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-WinADSiteConnections {
    [CmdletBinding()]
    param([alias('Joiner')][string] $Splitter,
        [switch] $Formatted)
    [Flags()]
    enum ConnectionOption {
        None
        IsGenerated
        TwoWaySync
        OverrideNotifyDefault = 4
        UseNotify = 8
        DisableIntersiteCompression = 16
        UserOwnedSchedule = 32
        RodcTopology = 64
    }
    $NamingContext = (Get-ADRootDSE).configurationNamingContext
    $Connections = Get-ADObject â€“Searchbase $NamingContext -LDAPFilter "(objectCategory=ntDSConnection)" -Properties *
    $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('Joiner')][string] $Splitter,
        [string] $Formatted)
    [Flags()]
    enum SiteLinksOptions {
        None = 0
        UseNotify = 1
        TwoWaySync = 2
        DisableCompression = 4
    }
    $NamingContext = (Get-ADRootDSE).configurationNamingContext
    $SiteLinks = Get-ADObject -LDAPFilter "(objectCategory=sitelink)" â€“Searchbase $NamingContext -Properties *
    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-WinADTombstoneLifetime {
    [cmdletBinding()]
    param()
    $Partition = $((Get-ADRootDSE).configurationNamingContext)
    (Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$Partition" -Properties tombstoneLifetime).tombstoneLifetime
}
function Get-WinADTrusts {
    [CmdletBinding()]
    param([string] $Forest,
        [alias('Domain')][string[]] $IncludeDomains,
        [string[]] $ExcludeDomains,
        [switch] $Display)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    foreach ($Domain in $ForestInformation.Domains) {
        $Trusts = Get-ADTrust -Server $Domain -Filter * -Properties *
        $DomainPDC = $ForestInformation[$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) {
            $TrustWMI = $TrustStatatuses | & { process { if ($_.TrustedDomain -eq $Trust.Target) { $_ } } }
            if ($Display) {
                [PsCustomObject] @{'Trust Source' = $Domain
                    'Trust Target'                = $Trust.Target
                    'Trust Direction'             = $Trust.Direction
                    'Trust Attributes'            = if ($Trust.TrustAttributes -is [int]) { Get-ADTrustAttributes -Value $Trust.TrustAttributes } else { 'Error - needs fixing' }
                    'Trust Status'                = if ($null -ne $TrustWMI) { $TrustWMI.TrustStatusString } else { 'N/A' }
                    'Forest Transitive'           = $Trust.ForestTransitive
                    'Selective Authentication'    = $Trust.SelectiveAuthentication
                    'SID Filtering Forest Aware'  = $Trust.SIDFilteringForestAware
                    'SID Filtering Quarantined'   = $Trust.SIDFilteringQuarantined
                    'Disallow Transivity'         = $Trust.DisallowTransivity
                    'Intra Forest'                = $Trust.IntraForest
                    'Is Tree Parent'              = $Trust.IsTreeParent
                    'Is Tree Root'                = $Trust.IsTreeRoot
                    'TGTDelegation'               = $Trust.TGTDelegation
                    'TrustedPolicy'               = $Trust.TrustedPolicy
                    'TrustingPolicy'              = $Trust.TrustingPolicy
                    'TrustType'                   = $Trust.TrustType
                    'UplevelOnly'                 = $Trust.UplevelOnly
                    'UsesAESKeys'                 = $Trust.UsesAESKeys
                    'UsesRC4Encryption'           = $Trust.UsesRC4Encryption
                    'Trust Source DC'             = if ($null -ne $TrustWMI) { $TrustWMI.PSComputerName } else { '' }
                    'Trust Target DC'             = if ($null -ne $TrustWMI) { $TrustWMI.TrustedDCName.Replace('\\', '') } else { '' }
                    'Trust Source DN'             = $Trust.Source
                    'ObjectGUID'                  = $Trust.ObjectGUID
                    'Created'                     = $Trust.Created
                    'Modified'                    = $Trust.Modified
                    'Deleted'                     = $Trust.Deleted
                    'SID'                         = $Trust.securityIdentifier
                    'TrustOK'                     = if ($null -ne $TrustWMI) { $TrustWMI.TrustIsOK } else { $false }
                    'TrustStatus'                 = if ($null -ne $TrustWMI) { $TrustWMI.TrustStatus } else { -1 }
                }
            } else {
                [PsCustomObject] @{'TrustSource' = $Domain
                    'TrustTarget'                = $Trust.Target
                    'TrustDirection'             = $Trust.Direction
                    '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
                    '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-WinADUsersForeignSecurityPrincipalList {
    [alias('Get-WinADUsersFP')]
    param([alias('ForestName')][string] $Forest,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [string[]] $ExcludeDomains)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    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 Repair-WinADEmailAddress {
    [CmdletBinding(SupportsShouldProcess = $True)]
    param([Microsoft.ActiveDirectory.Management.ADAccount] $ADUser,
        [string] $FromEmail,
        [string] $ToEmail,
        [switch] $Display)
    $ProcessUser = Get-WinADProxyAddresses -ADUser $ADUser -RemovePrefix
    if ($FromEmail -and $FromEmail -like '*@*') {
        if ($FromEmail -ne $ToEmail) {
            $FindSecondary = "SMTP:$FromEmail"
            if ($ProcessUser.Primary -contains $FromEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $FindSecondary will be removed from proxy addresses as primary (1)")) { Set-ADUser -Identity $ADUser -Remove @{proxyAddresses = $FindSecondary } } }
            $MakeSecondary = "smtp:$FromEmail"
            if ($ProcessUser.Secondary -notcontains $FromEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $MakeSecondary will be added to proxy addresses as secondary (2)")) { Set-ADUser -Identity $ADUser -Add @{proxyAddresses = $MakeSecondary } } }
        }
    }
    if ($ToEmail -and $ToEmail -like '*@*') {
        $RemovePotential = "smtp:$ToEmail"
        $MakePrimary = "SMTP:$ToEmail"
        if ($ProcessUser.EmailAddress -ne $ToEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $ToEmail will be set in EmailAddresss field (3)")) { Set-ADUser -Identity $ADUser -EmailAddress $ToEmail } }
        if ($ProcessUser.Secondary -contains $ToEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $RemovePotential will be removed from proxy addresses (4)")) { Set-ADUser -Identity $ADUser -Remove @{proxyAddresses = $RemovePotential } } }
        if ($ProcessUser.Primary -notcontains $ToEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $MakePrimary will be added to proxy addresses as primary (5)")) { Set-ADUser -Identity $ADUser -Add @{proxyAddresses = $MakePrimary } } }
    }
    if ($Display) { $ProcessUser }
}
function Set-WinADReplication {
    [CmdletBinding()]
    param([int] $ReplicationInterval = 15,
        [switch] $Instant)
    $NamingContext = (Get-ADRootDSE).configurationNamingContext
    Get-ADObject -LDAPFilter "(objectCategory=sitelink)" â€“Searchbase $NamingContext -Properties options | ForEach-Object { if ($Instant) {
            Set-ADObject $_ -replace @{replInterval = $ReplicationInterval }
            Set-ADObject $_ â€“replace @{options = $($_.options -bor 1) }
        } else { Set-ADObject $_ -replace @{replInterval = $ReplicationInterval } } }
}
function Set-WinADReplicationConnections {
    [CmdletBinding()]
    param([switch] $Force)
    [Flags()]
    enum ConnectionOption {
        None
        IsGenerated
        TwoWaySync
        OverrideNotifyDefault = 4
        UseNotify = 8
        DisableIntersiteCompression = 16
        UserOwnedSchedule = 32
        RodcTopology = 64
    }
    $NamingContext = (Get-ADRootDSE).configurationNamingContext
    $Connections = Get-ADObject â€“Searchbase $NamingContext -LDAPFilter "(objectCategory=ntDSConnection)" -Properties *
    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) }
        }
    }
}
function Set-WinADTombstoneLifetime {
    [cmdletBinding()]
    param([int] $Days)
    $Partition = $((Get-ADRootDSE).configurationNamingContext)
    Set-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$Partition" -Partition $Partition -Replace @{tombstonelifetime = $Days }
}
function Sync-DomainController {
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $DistinguishedName = (Get-ADDomain -Server $QueryServer).DistinguishedName
        ($ForestInformation["$Domain"]).Name | ForEach-Object { Write-Verbose -Message "Sync-DomainController - Forcing synchronization $_"
            repadmin /syncall $_ $DistinguishedName /e /A | Out-Null }
    }
}
function Test-ADDomainController {
    [CmdletBinding()]
    param([string[]] $ComputerName,
        [Parameter(Mandatory = $false)][PSCredential] $Credential = $null)
    $CredentialParameter = @{ }
    if ($null -ne $Credential) { $CredentialParameter['Credential'] = $Credential }
    $Output = foreach ($Computer in $ComputerName) {
        $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)
    $Roles = Get-WinADForestRoles -Forest $Forest -IncludeDomains $IncludeDomains -IncludeDomainControllers $IncludeDomainControllers -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -SkipRODC:$SkipRODC
    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([string] $Splitter)
    [Array] $SiteLinks = Get-WinADSiteConnections
    $Collection = @($SiteLinks).Where( { $_.Options -notcontains 'IsGenerated' -and $_.EnabledConnection -eq $true }, 'Split')
    $LinksManual = foreach ($Link in $Collection[0]) { "$($Link.ServerFrom) to $($Link.ServerTo)" }
    $LinksAutomatic = foreach ($Link in $Collection[1]) { "$($Link.ServerFrom) to $($Link.ServerTo)" }
    $CollectionNotifications = @($SiteLinks).Where( { $_.Options -notcontains 'UseNotify' -and $_.EnabledConnection -eq $true }, 'Split')
    $LinksNotUsingNotifications = foreach ($Link in $CollectionNotifications[0]) { "$($Link.ServerFrom) to $($Link.ServerTo)" }
    $LinksUsingNotifications = foreach ($Link in $CollectionNotifications[1]) { "$($Link.ServerFrom) to $($Link.ServerTo)" }
    [ordered] @{SiteLinksManual      = if ($Splitter -eq '') { $LinksManual } else { $LinksManual -join $Splitter }
        SiteLinksAutomatic           = if ($Splitter -eq '') { $LinksAutomatic } else { $LinksAutomatic -join $Splitter }
        SiteLinksUseNotify           = if ($Splitter -eq '') { $LinksUsingNotifications } else { $LinksUsingNotifications -join $Splitter }
        SiteLinksNotUsingNotify      = if ($Splitter -eq '') { $LinksNotUsingNotifications } else { $LinksNotUsingNotifications -join $Splitter }
        SiteLinksUseNotifyCount      = $CollectionNotifications[1].Count
        SiteLinksNotUsingNotifyCount = $CollectionNotifications[0].Count
        SiteLinksManualCount         = $Collection[0].Count
        SiteLinksAutomaticCount      = $Collection[1].Count
        SiteLinksTotalCount          = ($SiteLinks | Where-Object { $_.EnabledConnection -eq $true }).Count
    }
}
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 {
    [CmdletBinding()]
    param ([alias('Server', 'IpAddress')][Parameter(Mandatory = $True)][string[]]$ComputerName,
        [int] $GCPortLDAP = 3268,
        [int] $GCPortLDAPSSL = 3269,
        [int] $PortLDAP = 389,
        [int] $PortLDAPS = 636)
    foreach ($Computer in $ComputerName) {
        [Array] $ADServerFQDN = (Resolve-DnsName -Name $Computer -ErrorAction SilentlyContinue)
        if ($ADServerFQDN) {
            if ($ADServerFQDN.NameHost) { $ServerName = $ADServerFQDN[0].NameHost } else {
                [Array] $ADServerFQDN = (Resolve-DnsName -Name $Computer -ErrorAction SilentlyContinue)
                $FilterName = $ADServerFQDN | Where-Object { $_.QueryType -eq 'A' }
                $ServerName = $FilterName[0].Name
            }
        } else { $ServerName = '' }
        $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) { $GCPortLDAP }
            if ($GlobalCatalogSSL) { $GCPortLDAPSSL }
            if ($ConnectionLDAP) { $PortLDAP }
            if ($ConnectionLDAPS) { $PortLDAPS }) | Sort-Object
        [pscustomobject]@{Computer = $Computer
            ComputerFQDN           = $ServerName
            GlobalCatalogLDAP      = $GlobalCatalogNonSSL
            GlobalCatalogLDAPS     = $GlobalCatalogSSL
            LDAP                   = $ConnectionLDAP
            LDAPS                  = $ConnectionLDAPS
            AvailablePorts         = $PortsThatWork -join ','
        }
    }
}
Export-ModuleMember -Function @('Get-ADACL', 'Get-WinADBitlockerLapsSummary', 'Get-WinADDFSHealth', 'Get-WinADForestObjectsConflict', 'Get-WinADForestReplication', 'Get-WinADForestRoles', 'Get-WinADGPOMissingPermissions', 'Get-WinADGPOSysvolFolders', 'Get-WinADLastBackup', 'Get-WinADLMSettings', 'Get-WinADPriviligedObjects', 'Get-WinADProxyAddresses', 'Get-WinADSiteConnections', 'Get-WinADSiteLinks', 'Get-WinADTombstoneLifetime', 'Get-WinADTrusts', 'Get-WinADUsersForeignSecurityPrincipalList', 'Repair-WinADEmailAddress', 'Set-WinADReplication', 'Set-WinADReplicationConnections', 'Set-WinADTombstoneLifetime', 'Sync-DomainController', 'Test-ADDomainController', 'Test-ADRolesAvailability', 'Test-ADSiteLinks', 'Test-DNSNameServers', 'Test-FSMORolesAvailability', 'Test-LDAP') -Alias @('Get-WinADDomainRoles', 'Get-WinADGPOSysvol', 'Get-WinADRoles', 'Get-WinADUsersFP')