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') |