ADEssentials.psm1

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 (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.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]
    } elseif ($OperatingSystem -like '*Windows Server*') {
        $Systems = @{'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]
    } elseif ($OperatingSystem -notlike 'Windows 10*') { $System = $OperatingSystem }
    if ($System) { $System } else { 'Unknown' }
}
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-WinADForestControllers {
    [alias('Get-WinADDomainControllers')]
    <#
    .SYNOPSIS
 
 
    .DESCRIPTION
    Long description
 
    .PARAMETER TestAvailability
    Parameter description
 
    .EXAMPLE
    Get-WinADForestControllers -TestAvailability | Format-Table
 
    .EXAMPLE
    Get-WinADDomainControllers
 
    .EXAMPLE
    Get-WinADDomainControllers | Format-Table *
 
    Output:
 
    Domain HostName Forest IPV4Address IsGlobalCatalog IsReadOnly SchemaMaster DomainNamingMasterMaster PDCEmulator RIDMaster InfrastructureMaster Comment
    ------ -------- ------ ----------- --------------- ---------- ------------ ------------------------ ----------- --------- -------------------- -------
    ad.evotec.xyz AD1.ad.evotec.xyz ad.evotec.xyz 192.168.240.189 True False True True True True True
    ad.evotec.xyz AD2.ad.evotec.xyz ad.evotec.xyz 192.168.240.192 True False False False False False False
    ad.evotec.pl ad.evotec.xyz False False False False False Unable to contact the server. This may be becau...
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string[]] $Domain,
        [switch] $TestAvailability,
        [switch] $SkipEmpty)
    try {
        $Forest = Get-ADForest
        if (-not $Domain) { $Domain = $Forest.Domains }
    } catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        Write-Warning "Get-WinADForestControllers - Couldn't use Get-ADForest feature. Error: $ErrorMessage"
        return
    }
    $Servers = foreach ($D in $Domain) {
        try {
            $DC = Get-ADDomainController -Server $D -Filter *
            foreach ($S in $DC) {
                $Server = [ordered] @{Domain = $D
                    HostName                 = $S.HostName
                    Name                     = $S.Name
                    Forest                   = $Forest.RootDomain
                    IPV4Address              = $S.IPV4Address
                    IPV6Address              = $S.IPV6Address
                    IsGlobalCatalog          = $S.IsGlobalCatalog
                    IsReadOnly               = $S.IsReadOnly
                    Site                     = $S.Site
                    SchemaMaster             = ($S.OperationMasterRoles -contains 'SchemaMaster')
                    DomainNamingMaster       = ($S.OperationMasterRoles -contains 'DomainNamingMaster')
                    PDCEmulator              = ($S.OperationMasterRoles -contains 'PDCEmulator')
                    RIDMaster                = ($S.OperationMasterRoles -contains 'RIDMaster')
                    InfrastructureMaster     = ($S.OperationMasterRoles -contains 'InfrastructureMaster')
                    LdapPort                 = $S.LdapPort
                    SslPort                  = $S.SslPort
                    Pingable                 = $null
                    Comment                  = ''
                }
                if ($TestAvailability) { $Server['Pingable'] = foreach ($_ in $Server.IPV4Address) { Test-Connection -Count 1 -Server $_ -Quiet -ErrorAction SilentlyContinue } }
                [PSCustomObject] $Server
            }
        } catch {
            [PSCustomObject]@{Domain     = $D
                HostName                 = ''
                Name                     = ''
                Forest                   = $Forest.RootDomain
                IPV4Address              = ''
                IPV6Address              = ''
                IsGlobalCatalog          = ''
                IsReadOnly               = ''
                Site                     = ''
                SchemaMaster             = $false
                DomainNamingMasterMaster = $false
                PDCEmulator              = $false
                RIDMaster                = $false
                InfrastructureMaster     = $false
                LdapPort                 = ''
                SslPort                  = ''
                Pingable                 = $null
                Comment                  = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            }
        }
    }
    if ($SkipEmpty) { return $Servers | Where-Object { $_.HostName -ne '' } }
    return $Servers
}
function 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-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-WinADBitlockerLapsSummary {
    [CmdletBinding()]
    param([string[]] $Domain)
    if (-not $Domain) { $Domain = (Get-ADForest).Domains }
    $ComputerProperties = @($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'
            'ms-Mcs-AdmPwd'
            'ms-Mcs-AdmPwdExpirationTime')
    } else {
        $LapsAvailable = $false
        $Properties = @('Name'
            'OperatingSystem'
            'OperatingSystemVersion'
            'DistinguishedName'
            'LastLogonDate')
    }
    $CurrentDate = Get-Date
    $FormattedComputers = foreach ($D in $Domain) {
        $Computers = Get-ADComputer -Filter * -Properties $Properties -Server $D
        foreach ($_ in $Computers) {
            if ($LapsAvailable) {
                if ($_.'ms-Mcs-AdmPwd') {
                    $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' }
            [Array] $Bitlockers = Get-ADObject -Server $D -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
            }
            [PSCustomObject] @{Name = $_.Name
                Enabled             = $_.Enabled
                Domain              = $D
                DNSHostName         = $_.DNSHostName
                DistinguishedName   = $_.DistinguishedName
                System              = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
                LastLogonDate       = $_.LastLogonDate
                Encrypted           = $Encrypted
                EncryptedTime       = $EncryptedTime
                Laps                = $Laps
                LapsExpirationDays  = $LapsExpirationDays
                LapsExpirationTime  = $LapsExpirationTime
            }
        }
    }
    return $FormattedComputers
}
function Get-WinADDFSHealth {
    [cmdletBinding()]
    param([string[]] $Domains,
        [Array] $DomainControllers,
        [int] $EventDays = 1)
    $Today = (Get-Date)
    $Yesterday = (Get-Date -Hour 0 -Second 0 -Minute 0 -Millisecond 0).AddDays(-$EventDays)
    if (-not $Domains) {
        $Forest = Get-ADForest
        $Domains = $Forest.Domains
    }
    [Array] $Table = foreach ($Domain in $Domains) {
        Write-Verbose "Get-WinADDFSHealth - Processing $Domain"
        if (-not $DomainControllers) { $DomainControllers = Get-ADDomainController -Filter * -Server $Domain } else { $DomainControllers = foreach ($_ in $DomainControllers) { Get-ADDomainController -Identity $_ -Server $Domain } }
        [Array]$GPOs = @(Get-GPO -All -Domain $Domain)
        try {
            $CentralRepository = Get-ChildItem -Path "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" -ErrorAction Stop
            $CentralRepositoryDomain = if ($CentralRepository) { $true } else { $false }
        } catch { $CentralRepositoryDomain = $false }
        foreach ($DC in $DomainControllers) {
            Write-Verbose "Get-WinADDFSHealth - Processing $DC for $Domain"
            $DCName = $DC.Name
            $Hostname = $DC.Hostname
            $DN = $DC.ComputerObjectDN
            $LocalSettings = "CN=DFSR-LocalSettings,$DN"
            $Subscriber = "CN=Domain System Volume,$LocalSettings"
            $Subscription = "CN=SYSVOL Subscription,$Subscriber"
            $ReplicationStatus = @{'0' = 'Uninitialized'
                '1'                    = 'Initialized'
                '2'                    = 'Initial synchronization'
                '3'                    = 'Auto recovery'
                '4'                    = 'Normal'
                '5'                    = 'In error state'
                '6'                    = 'Disabled'
                '7'                    = 'Unknown'
            }
            $DomainSummary = [ordered] @{"DomainController" = $DCName
                "Domain"                                    = $Domain
                "Status"                                    = $false
                "ReplicationState"                          = 'Unknown'
                "IsPDC"                                     = $DC.OperationMasterRoles -contains 'PDCEmulator'
                "GroupPolicyCount"                          = $GPOs.Count
                "SYSVOLCount"                               = 0
                CentralRepository                           = $CentralRepositoryDomain
                CentralRepositoryDC                         = $false
                'IdenticalCount'                            = $false
                "Availability"                              = $false
                "MemberReference"                           = $false
                "DFSErrors"                                 = 0
                "DFSEvents"                                 = $null
                "DFSLocalSetting"                           = $false
                "DomainSystemVolume"                        = $false
                "SYSVOLSubscription"                        = $false
                "StopReplicationOnAutoRecovery"             = $false
            }
            $DFSReplicatedFolderInfo = Get-CimData -NameSpace "root\microsoftdfs" -Class 'dfsrreplicatedfolderinfo' -ComputerName $Hostname
            $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 $Domain -ErrorAction Stop).'msDFSR-MemberReference' -like "CN=$DCName,*"
                $DomainSummary['MemberReference'] = if ($MemberReference) { $true } else { $false }
            } catch { $DomainSummary['MemberReference'] = $false }
            try {
                $DFSLocalSetting = Get-ADObject $LocalSettings -Server $Domain -ErrorAction Stop
                $DomainSummary['DFSLocalSetting'] = if ($DFSLocalSetting) { $true } else { $false }
            } catch { $DomainSummary['DFSLocalSetting'] = $false }
            try {
                $DomainSystemVolume = Get-ADObject $Subscriber -Server $Domain -ErrorAction Stop
                $DomainSummary['DomainSystemVolume'] = if ($DomainSystemVolume) { $true } else { $false }
            } catch { $DomainSummary['DomainSystemVolume'] = $false }
            try {
                $SysVolSubscription = Get-ADObject $Subscription -Server $Domain -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']
            $Registry = Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\DFSR\Parameters" -ComputerName $Hostname
            if ($null -ne $Registry.StopReplicationOnAutoRecovery) { $DomainSummary['StopReplicationOnAutoRecovery'] = [bool] $Registry.StopReplicationOnAutoRecovery } else { $DomainSummary['StopReplicationOnAutoRecovery'] = $null }
            $All = @($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()
    $Forest = Get-ADForest
    foreach ($Domain in $Forest.Domains) {
        $DC = Get-ADDomainController -DomainName $Domain -Discover
        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([switch] $Extended,
        [Array] $DomainControllers)
    if (-not $DomainControllers) { $DomainControllers = Get-WinADForestControllers }
    $ProcessErrors = [System.Collections.Generic.List[PSCustomObject]]::new()
    $Replication = foreach ($DC in $DomainControllers) {
        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-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([string] $Domain = $Env:USERDNSDOMAIN)
    $GPOs = Get-GPO -All -Domain $Domain
    $MissingPermissions = @(foreach ($GPO in $GPOs) {
            If ($GPO.User.Enabled) {
                $GPOPermissionForAuthUsers = Get-GPPermission -Guid $GPO.Id -All | Select-Object -ExpandProperty Trustee | Where-Object { $_.Name -eq "Authenticated Users" }
                $GPOPermissionForDomainComputers = Get-GPPermission -Guid $GPO.Id -All | 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([Array] $GPOs,
        [string] $Domain = $EnV:USERDNSDOMAIN,
        [Array] $ComputerName)
    if (-not $ComputerName) { $ComputerName = Get-ADDomainController -Filter * -Server $Domain -ErrorAction SilentlyContinue } else { $ComputerName = foreach ($_ in $ComputerName) { Get-ADDomainController -Identity $_ -ErrorAction SilentlyContinue -Server $Domain } }
    if (-not $GPOs) { [Array]$GPOs = @(Get-GPO -All -Domain $Domain) }
    foreach ($Server in $ComputerName) {
        $Differences = @{ }
        $SysvolHash = @{ }
        $GPOGUIDS = $GPOs.ID.GUID
        try { $SYSVOL = Get-ChildItem -Path "\\$Server\SYSVOL\$Domain\Policies" -ErrorAction Stop } catch { $Sysvol = $Null }
        foreach ($_ in $SYSVOL) {
            $GUID = $_.Name -replace '{' -replace '}'
            $SysvolHash[$GUID] = $_
        }
        $Files = $SYSVOL.Name -replace '{' -replace '}'
        if ($Files -ne '') {
            $Comparing = Compare-Object -ReferenceObject $GPOGUIDS -DifferenceObject $Files -IncludeEqual
            foreach ($_ in $Comparing) {
                if ($_.SideIndicator -eq '==') { $Found = 'Exists' } elseif ($_.SideIndicator -eq '<=') { $Found = 'Not available on SYSVOL' } else { $Found = 'Orphaned GPO' }
                $Differences[$_.InputObject] = $Found
            }
        } else { }
        $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 }
                [PSCustomObject] @{DisplayName = $GPO.DisplayName
                    DomainName                 = $GPO.DomainName
                    SysvolServer               = $Server.HostName
                    SysvolStatus               = if ($null -eq $Differences[$GPO.Id.Guid]) { 'Not available on SYSVOL' } else { $Differences[$GPO.Id.Guid] }
                    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
                            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
    }
}
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([string[]] $Domains)
    $NameUsed = [System.Collections.Generic.List[string]]::new()
    [DateTime] $CurrentDate = Get-Date
    if (-not $Domains) {
        try {
            $Forest = Get-ADForest -ErrorAction Stop
            $Domains = $Forest.Domains
        } catch { Write-Warning "Get-WinADLastBackup - Failed to gather Forest Domains $($_.Exception.Message)" }
    }
    foreach ($Domain in $Domains) {
        try {
            [string[]]$Partitions = (Get-ADRootDSE -Server $Domain -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([switch] $LegitimateOnly,
        [switch] $OrphanedOnly,
        [switch] $Unique,
        [switch] $SummaryOnly)
    $Forest = Get-ADForest
    $Domains = $Forest.Domains
    $UsersWithAdminCount = foreach ($domain in $Domains) {
        $Objects = Get-ADObject -filter 'admincount -eq 1 -and iscriticalsystemobject -notlike "*"' -server $domain -properties whenchanged, whencreated, admincount, isCriticalSystemObject, "msDS-ReplAttributeMetaData", samaccountname
        foreach ($_ in $Objects) {
            [PSCustomObject] @{Domain  = $Domain
                distinguishedname      = $_.distinguishedname
                whenchanged            = $_.whenchanged
                whencreated            = $_.whencreated
                admincount             = $_.admincount
                SamAccountName         = $_.SamAccountName
                objectclass            = $_.objectclass
                isCriticalSystemObject = $_.isCriticalSystemObject
                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) { Get-ADGroup -filter 'admincount -eq 1 -and iscriticalsystemobject -eq $true' -server $domain | Select-Object @{name = 'Domain'; expression = { $domain } }, distinguishedname }
    $AdminCountLegitimate = [System.Collections.Generic.List[PSCustomObject]]::new()
    $AdminCountOrphaned = [System.Collections.Generic.List[PSCustomObject]]::new()
    $AdminCountAll = foreach ($object in $UsersWithAdminCount) {
        $DistinguishedName = ($object).distinguishedname
        $Results = foreach ($Group in $CriticalGroups) {
            $IsMember = if (Get-ADGroup -Filter { Member -RecursiveMatch $DistinguishedName } -searchbase $Group.DistinguishedName -server $Group.Domain) { $True } else { $False }
            $User = [PSCustomObject] @{DistinguishedName = $Object.DistinguishedName
                Domain                                   = $Object.domain
                IsMember                                 = $IsMember
                Admincount                               = $Object.admincount
                AdminCountDate                           = $Object.adminCountDate
                Whencreated                              = $Object.whencreated
                ObjectClass                              = $Object.objectclass
                GroupDomain                              = if ($IsMember) { $Group.Domain } else { $null }
                GroupDistinguishedname                   = if ($IsMember) { $Group.DistinguishedName } else { $null }
            }
            if ($User.IsMember) {
                $AdminCountLegitimate.Add($User)
                $User
            }
            if ($User.IsMember -eq $false -and $AdminCountLegitimate.DistinguishedName -notcontains $User.DistinguishedName -and $AdminCountOrphaned.DistinguishedName -notcontains $User.DistinguishedName) {
                $Properties = @('distinguishedname'
                    'domain'
                    'IsMember'
                    'admincount'
                    'adminCountDate'
                    'whencreated'
                    'objectclass')
                $AdminCountOrphaned.Add(($User | Select-Object -Property $Properties))
                $User
            }
        }
        $Results
    }
    $Output = @(if ($OrphanedOnly) { $AdminCountOrphaned } elseif ($LegitimateOnly) { if ($Unique) { $AdminCountLegitimate | Select-Object -Property DistinguishedName, Domain, IsMember, Admincount, AdminCountDate, Whencreated, ObjectClass -Unique } else { $AdminCountLegitimate } } else { if ($Unique) { $AdminCountAll | Select-Object -Property DistinguishedName, Domain, IsMember, Admincount, AdminCountDate, Whencreated, ObjectClass -Unique } 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-WinADUsersForeignSecurityPrincipalList {
    [alias('Get-WinADUsersFP')]
    param([string] $Domain)
    $ForeignSecurityPrincipalList = Get-ADObject -Filter { ObjectClass -eq 'ForeignSecurityPrincipal' } -Properties * -Server $Domain
    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 Sync-DomainController {
    [CmdletBinding()]
    param([string] $Domain = $Env:USERDNSDOMAIN)
    $DistinguishedName = (Get-ADDomain -Server $Domain).DistinguishedName
    (Get-ADDomainController -Filter * -Server $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([string] $Domain)
    $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    $Roles = & $ADModule { param($Domain); Get-WinADForestRoles -Domain $Domain } $Domain
    if ($Domain -ne '') {
        [PSCustomObject] @{PDCEmulator       = $Roles['PDCEmulator']
            PDCEmulatorAvailability          = if ($Roles['PDCEmulator']) { (Test-NetConnection -ComputerName $Roles['PDCEmulator']).PingSucceeded } else { $false }
            RIDMaster                        = $Roles['RIDMaster']
            RIDMasterAvailability            = if ($Roles['RIDMaster']) { (Test-NetConnection -ComputerName $Roles['RIDMaster']).PingSucceeded } else { $false }
            InfrastructureMaster             = $Roles['InfrastructureMaster']
            InfrastructureMasterAvailability = if ($Roles['InfrastructureMaster']) { (Test-NetConnection -ComputerName $Roles['InfrastructureMaster']).PingSucceeded } else { $false }
        }
    } else {
        [PSCustomObject] @{SchemaMaster    = $Roles['SchemaMaster']
            SchemaMasterAvailability       = if ($Roles['SchemaMaster']) { (Test-NetConnection -ComputerName $Roles['SchemaMaster']).PingSucceeded } else { $false }
            DomainNamingMaster             = $Roles['DomainNamingMaster']
            DomainNamingMasterAvailability = if ($Roles['DomainNamingMaster']) { (Test-NetConnection -ComputerName $Roles['DomainNamingMaster']).PingSucceeded } else { $false }
        }
    }
}
function Test-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-WinADBitlockerLapsSummary', 'Get-WinADDFSHealth', 'Get-WinADForestObjectsConflict', 'Get-WinADForestReplication', 'Get-WinADGPOMissingPermissions', 'Get-WinADGPOSysvolFolders', 'Get-WinADLastBackup', 'Get-WinADLMSettings', 'Get-WinADPriviligedObjects', 'Get-WinADProxyAddresses', 'Get-WinADSiteConnections', 'Get-WinADSiteLinks', 'Get-WinADUsersForeignSecurityPrincipalList', 'Repair-WinADEmailAddress', 'Set-WinADReplication', 'Set-WinADReplicationConnections', 'Sync-DomainController', 'Test-ADDomainController', 'Test-ADRolesAvailability', 'Test-ADSiteLinks', 'Test-DNSNameServers', 'Test-FSMORolesAvailability', 'Test-LDAP') -Alias @('Get-WinADGPOSysvol', 'Get-WinADUsersFP')