ADEssentials.psm1

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" -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-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")
            $LastBackup = [DateTime] $($dsaSignature.LastOriginatingChangeTime)
            [PSCustomObject] @{Domain = $Domain
                NamingContext = $Name
                LastBackup = $LastBackup
                LastBackupDaysAgo = - (Convert-TimeToDays -StartTime ($CurrentDate) -EndTime ($LastBackup))
            }
        }
        $Output
    }
}
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-WinADSiteConnections {
    [CmdletBinding()]
    param([alias('Joiner')][string] $Splitter,
        [string] $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 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-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 ','
        }
    }
}
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
    }
}
Export-ModuleMember -Function @('Get-WinADDFSHealth', 'Get-WinADForestReplication', 'Get-WinADGPOMissingPermissions', 'Get-WinADGPOSysvolFolders', 'Get-WinADLastBackup', 'Get-WinADPriviligedObjects', 'Get-WinADSiteConnections', 'Get-WinADSiteLinks', 'Get-WinADUsersForeignSecurityPrincipalList', 'Set-WinADReplication', 'Set-WinADReplicationConnections', 'Sync-DomainController', 'Test-ADRolesAvailability', 'Test-ADSiteLinks', 'Test-DNSNameServers', 'Test-FSMORolesAvailability', 'Test-LDAP') -Alias @('Get-WinADGPOSysvol', 'Get-WinADUsersFP')