ADEssentials.psm1
function ConvertFrom-DistinguishedName { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER DistinguishedName Parameter description .PARAMETER ToOrganizationalUnit Parameter description .PARAMETER ToDC Parameter description .PARAMETER ToDomainCN Parameter description .EXAMPLE $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToOrganizationalUnit Output: OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName Output: Przemyslaw Klys .NOTES General notes #> [CmdletBinding()] param([alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName, [switch] $ToOrganizationalUnit, [switch] $ToDC, [switch] $ToDomainCN) process { foreach ($Distinguished in $DistinguishedName) { if ($ToDomainCN) { $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' $CN = $DN -replace ',DC=', '.' -replace "DC=" $CN } elseif ($ToOrganizationalUnit) { [Regex]::Match($Distinguished, '(?=OU=)(.*\n?)(?<=.)').Value } elseif ($ToDC) { $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' } else { $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$' $Output = foreach ($_ in $Distinguished) { $_ -match $Regex $Matches } $Output.cn } } } } function ConvertFrom-SID { [cmdletbinding()] param([string[]] $SID, [switch] $OnlyWellKnown, [switch] $OnlyWellKnownAdministrative) $WellKnownAdministrative = @{'S-1-5-18' = 'NT AUTHORITY\SYSTEM' } $wellKnownSIDs = @{'S-1-0' = 'Null AUTHORITY' 'S-1-0-0' = 'NULL SID' 'S-1-1' = 'WORLD AUTHORITY' 'S-1-1-0' = 'Everyone' 'S-1-2' = 'LOCAL AUTHORITY' 'S-1-2-0' = 'LOCAL' 'S-1-2-1' = 'CONSOLE LOGON' 'S-1-3' = 'CREATOR AUTHORITY' 'S-1-3-0' = 'CREATOR OWNER' 'S-1-3-1' = 'CREATOR GROUP' 'S-1-3-2' = 'CREATOR OWNER SERVER' 'S-1-3-3' = 'CREATOR GROUP SERVER' 'S-1-3-4' = 'OWNER RIGHTS' 'S-1-5-80-0' = 'NT SERVICE\ALL SERVICES' 'S-1-4' = 'Non-unique Authority' 'S-1-5' = 'NT AUTHORITY' 'S-1-5-1' = 'NT AUTHORITY\DIALUP' 'S-1-5-2' = 'NT AUTHORITY\NETWORK' 'S-1-5-3' = 'NT AUTHORITY\BATCH' 'S-1-5-4' = 'NT AUTHORITY\INTERACTIVE' 'S-1-5-6' = 'NT AUTHORITY\SERVICE' 'S-1-5-7' = 'NT AUTHORITY\ANONYMOUS LOGON' 'S-1-5-8' = 'NT AUTHORITY\PROXY' 'S-1-5-9' = 'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS' 'S-1-5-10' = 'NT AUTHORITY\SELF' 'S-1-5-11' = 'NT AUTHORITY\Authenticated Users' 'S-1-5-12' = 'NT AUTHORITY\RESTRICTED' 'S-1-5-13' = 'NT AUTHORITY\TERMINAL SERVER USER' 'S-1-5-14' = 'NT AUTHORITY\REMOTE INTERACTIVE LOGON' 'S-1-5-15' = 'NT AUTHORITY\This Organization' 'S-1-5-17' = 'NT AUTHORITY\IUSR' 'S-1-5-18' = 'NT AUTHORITY\SYSTEM' 'S-1-5-19' = 'NT AUTHORITY\NETWORK SERVICE' 'S-1-5-20' = 'NT AUTHORITY\NETWORK SERVICE' 'S-1-5-32-544' = 'BUILTIN\Administrators' 'S-1-5-32-545' = 'BUILTIN\Users' 'S-1-5-32-546' = 'BUILTIN\Guests' 'S-1-5-32-547' = 'BUILTIN\Power Users' 'S-1-5-32-548' = 'BUILTIN\Account Operators' 'S-1-5-32-549' = 'BUILTIN\Server Operators' 'S-1-5-32-550' = 'BUILTIN\Print Operators' 'S-1-5-32-551' = 'BUILTIN\Backup Operators' 'S-1-5-32-552' = 'BUILTIN\Replicators' 'S-1-5-64-10' = 'NT AUTHORITY\NTLM Authentication' 'S-1-5-64-14' = 'NT AUTHORITY\SChannel Authentication' 'S-1-5-64-21' = 'NT AUTHORITY\Digest Authentication' 'S-1-5-80' = 'NT SERVICE' 'S-1-5-83-0' = 'NT VIRTUAL MACHINE\Virtual Machines' 'S-1-16-0' = 'Untrusted Mandatory Level' 'S-1-16-4096' = 'Low Mandatory Level' 'S-1-16-8192' = 'Medium Mandatory Level' 'S-1-16-8448' = 'Medium Plus Mandatory Level' 'S-1-16-12288' = 'High Mandatory Level' 'S-1-16-16384' = 'System Mandatory Level' 'S-1-16-20480' = 'Protected Process Mandatory Level' 'S-1-16-28672' = 'Secure Process Mandatory Level' 'S-1-5-32-554' = 'BUILTIN\Pre-Windows 2000 Compatible Access' 'S-1-5-32-555' = 'BUILTIN\Remote Desktop Users' 'S-1-5-32-556' = 'BUILTIN\Network Configuration Operators' 'S-1-5-32-557' = 'BUILTIN\Incoming Forest Trust Builders' 'S-1-5-32-558' = 'BUILTIN\Performance Monitor Users' 'S-1-5-32-559' = 'BUILTIN\Performance Log Users' 'S-1-5-32-560' = 'BUILTIN\Windows Authorization Access Group' 'S-1-5-32-561' = 'BUILTIN\Terminal Server License Servers' 'S-1-5-32-562' = 'BUILTIN\Distributed COM Users' 'S-1-5-32-569' = 'BUILTIN\Cryptographic Operators' 'S-1-5-32-573' = 'BUILTIN\Event Log Readers' 'S-1-5-32-574' = 'BUILTIN\Certificate Service DCOM Access' 'S-1-5-32-575' = 'BUILTIN\RDS Remote Access Servers' 'S-1-5-32-576' = 'BUILTIN\RDS Endpoint Servers' 'S-1-5-32-577' = 'BUILTIN\RDS Management Servers' 'S-1-5-32-578' = 'BUILTIN\Hyper-V Administrators' 'S-1-5-32-579' = 'BUILTIN\Access Control Assistance Operators' 'S-1-5-32-580' = 'BUILTIN\Remote Management Users' } foreach ($S in $SID) { if ($OnlyWellKnownAdministrative) { if ($WellKnownAdministrative[$S]) { [PSCustomObject] @{Name = $WellKnownAdministrative[$S] SID = $S Type = 'WellKnownAdministrative' Error = '' } } } elseif ($OnlyWellKnown) { if ($wellKnownSIDs[$S]) { [PSCustomObject] @{Name = $wellKnownSIDs[$S] SID = $S Type = 'WellKnownGroup' Error = '' } } } else { if ($wellKnownSIDs[$S]) { [PSCustomObject] @{Name = $wellKnownSIDs[$S] SID = $S Type = 'WellKnownGroup' Error = '' } } else { try { [PSCustomObject] @{Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value SID = $S Type = 'Standard' Error = '' } } catch { [PSCustomObject] @{Name = $S SID = $S Error = $_.Exception.Message -replace [environment]::NewLine, ' ' Type = 'Unknown' } } } } } } function Convert-Identity { [cmdletBinding(DefaultParameterSetName = 'Identity')] param([parameter(ParameterSetName = 'Identity', Position = 0)][string[]] $Identity, [parameter(ParameterSetName = 'SID', Mandatory)][System.Security.Principal.SecurityIdentifier[]] $SID, [parameter(ParameterSetName = 'Name', Mandatory)][string[]] $Name) Begin { if (-not $Script:GlobalCacheSidConvert) { $Script:GlobalCacheSidConvert = @{'S-1-0' = 'Null AUTHORITY' 'S-1-0-0' = 'NULL SID' 'S-1-1' = 'WORLD AUTHORITY' 'S-1-1-0' = 'Everyone' 'S-1-2' = 'LOCAL AUTHORITY' 'S-1-2-0' = 'LOCAL' 'S-1-2-1' = 'CONSOLE LOGON' 'S-1-3' = 'CREATOR AUTHORITY' 'S-1-3-0' = 'CREATOR OWNER' 'S-1-3-1' = 'CREATOR GROUP' 'S-1-3-2' = 'CREATOR OWNER SERVER' 'S-1-3-3' = 'CREATOR GROUP SERVER' 'S-1-3-4' = 'OWNER RIGHTS' 'S-1-5-80-0' = 'NT SERVICE\ALL SERVICES' 'S-1-4' = 'Non-unique Authority' 'S-1-5' = 'NT AUTHORITY' 'S-1-5-1' = 'NT AUTHORITY\DIALUP' 'S-1-5-2' = 'NT AUTHORITY\NETWORK' 'S-1-5-3' = 'NT AUTHORITY\BATCH' 'S-1-5-4' = 'NT AUTHORITY\INTERACTIVE' 'S-1-5-6' = 'NT AUTHORITY\SERVICE' 'S-1-5-7' = 'NT AUTHORITY\ANONYMOUS LOGON' 'S-1-5-8' = 'NT AUTHORITY\PROXY' 'S-1-5-9' = 'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS' 'S-1-5-10' = 'NT AUTHORITY\SELF' 'S-1-5-11' = 'NT AUTHORITY\Authenticated Users' 'S-1-5-12' = 'NT AUTHORITY\RESTRICTED' 'S-1-5-13' = 'NT AUTHORITY\TERMINAL SERVER USER' 'S-1-5-14' = 'NT AUTHORITY\REMOTE INTERACTIVE LOGON' 'S-1-5-15' = 'NT AUTHORITY\This Organization' 'S-1-5-17' = 'NT AUTHORITY\IUSR' 'S-1-5-18' = 'NT AUTHORITY\SYSTEM' 'S-1-5-19' = 'NT AUTHORITY\NETWORK SERVICE' 'S-1-5-20' = 'NT AUTHORITY\NETWORK SERVICE' 'S-1-5-32-544' = 'BUILTIN\Administrators' 'S-1-5-32-545' = 'BUILTIN\Users' 'S-1-5-32-546' = 'BUILTIN\Guests' 'S-1-5-32-547' = 'BUILTIN\Power Users' 'S-1-5-32-548' = 'BUILTIN\Account Operators' 'S-1-5-32-549' = 'BUILTIN\Server Operators' 'S-1-5-32-550' = 'BUILTIN\Print Operators' 'S-1-5-32-551' = 'BUILTIN\Backup Operators' 'S-1-5-32-552' = 'BUILTIN\Replicators' 'S-1-5-64-10' = 'NT AUTHORITY\NTLM Authentication' 'S-1-5-64-14' = 'NT AUTHORITY\SChannel Authentication' 'S-1-5-64-21' = 'NT AUTHORITY\Digest Authentication' 'S-1-5-80' = 'NT SERVICE' 'S-1-5-83-0' = 'NT VIRTUAL MACHINE\Virtual Machines' 'S-1-16-0' = 'Untrusted Mandatory Level' 'S-1-16-4096' = 'Low Mandatory Level' 'S-1-16-8192' = 'Medium Mandatory Level' 'S-1-16-8448' = 'Medium Plus Mandatory Level' 'S-1-16-12288' = 'High Mandatory Level' 'S-1-16-16384' = 'System Mandatory Level' 'S-1-16-20480' = 'Protected Process Mandatory Level' 'S-1-16-28672' = 'Secure Process Mandatory Level' 'S-1-5-32-554' = 'BUILTIN\Pre-Windows 2000 Compatible Access' 'S-1-5-32-555' = 'BUILTIN\Remote Desktop Users' 'S-1-5-32-556' = 'BUILTIN\Network Configuration Operators' 'S-1-5-32-557' = 'BUILTIN\Incoming Forest Trust Builders' 'S-1-5-32-558' = 'BUILTIN\Performance Monitor Users' 'S-1-5-32-559' = 'BUILTIN\Performance Log Users' 'S-1-5-32-560' = 'BUILTIN\Windows Authorization Access Group' 'S-1-5-32-561' = 'BUILTIN\Terminal Server License Servers' 'S-1-5-32-562' = 'BUILTIN\Distributed COM Users' 'S-1-5-32-569' = 'BUILTIN\Cryptographic Operators' 'S-1-5-32-573' = 'BUILTIN\Event Log Readers' 'S-1-5-32-574' = 'BUILTIN\Certificate Service DCOM Access' 'S-1-5-32-575' = 'BUILTIN\RDS Remote Access Servers' 'S-1-5-32-576' = 'BUILTIN\RDS Endpoint Servers' 'S-1-5-32-577' = 'BUILTIN\RDS Management Servers' 'S-1-5-32-578' = 'BUILTIN\Hyper-V Administrators' 'S-1-5-32-579' = 'BUILTIN\Access Control Assistance Operators' 'S-1-5-32-580' = 'BUILTIN\Remote Management Users' } } $wellKnownSIDs = @{'S-1-0' = 'WellKnown' 'S-1-0-0' = 'WellKnown' 'S-1-1' = 'WellKnown' 'S-1-1-0' = 'WellKnown' 'S-1-2' = 'WellKnown' 'S-1-2-0' = 'WellKnown' 'S-1-2-1' = 'WellKnown' 'S-1-3' = 'WellKnown' 'S-1-3-0' = 'WellKnown' 'S-1-3-1' = 'WellKnown' 'S-1-3-2' = 'WellKnown' 'S-1-3-3' = 'WellKnown' 'S-1-3-4' = 'WellKnown' 'S-1-5-80-0' = 'WellKnown' 'S-1-4' = 'WellKnown' 'S-1-5' = 'WellKnown' 'S-1-5-1' = 'WellKnown' 'S-1-5-2' = 'WellKnown' 'S-1-5-3' = 'WellKnown' 'S-1-5-4' = 'WellKnown' 'S-1-5-6' = 'WellKnown' 'S-1-5-7' = 'WellKnown' 'S-1-5-8' = 'WellKnown' 'S-1-5-9' = 'WellKnown' 'S-1-5-10' = 'WellKnown' 'S-1-5-11' = 'WellKnown' 'S-1-5-12' = 'WellKnown' 'S-1-5-13' = 'WellKnown' 'S-1-5-14' = 'WellKnown' 'S-1-5-15' = 'WellKnown' 'S-1-5-17' = 'WellKnown' 'S-1-5-18' = 'WellKnownAdministrative' 'S-1-5-19' = 'WellKnown' 'S-1-5-20' = 'WellKnown' 'S-1-5-32-544' = 'WellKnownAdministrative' 'S-1-5-32-545' = 'WellKnown' 'S-1-5-32-546' = 'WellKnown' 'S-1-5-32-547' = 'WellKnown' 'S-1-5-32-548' = 'WellKnown' 'S-1-5-32-549' = 'WellKnown' 'S-1-5-32-550' = 'WellKnown' 'S-1-5-32-551' = 'WellKnown' 'S-1-5-32-552' = 'WellKnown' 'S-1-5-64-10' = 'WellKnown' 'S-1-5-64-14' = 'WellKnown' 'S-1-5-64-21' = 'WellKnown' 'S-1-5-80' = 'WellKnown' 'S-1-5-83-0' = 'WellKnown' 'S-1-16-0' = 'WellKnown' 'S-1-16-4096' = 'WellKnown' 'S-1-16-8192' = 'WellKnown' 'S-1-16-8448' = 'WellKnown' 'S-1-16-12288' = 'WellKnown' 'S-1-16-16384' = 'WellKnown' 'S-1-16-20480' = 'WellKnown' 'S-1-16-28672' = 'WellKnown' 'S-1-5-32-554' = 'WellKnown' 'S-1-5-32-555' = 'WellKnown' 'S-1-5-32-556' = 'WellKnown' 'S-1-5-32-557' = 'WellKnown' 'S-1-5-32-558' = 'WellKnown' 'S-1-5-32-559' = 'WellKnown' 'S-1-5-32-560' = 'WellKnown' 'S-1-5-32-561' = 'WellKnown' 'S-1-5-32-562' = 'WellKnown' 'S-1-5-32-569' = 'WellKnown' 'S-1-5-32-573' = 'WellKnown' 'S-1-5-32-574' = 'WellKnown' 'S-1-5-32-575' = 'WellKnown' 'S-1-5-32-576' = 'WellKnown' 'S-1-5-32-577' = 'WellKnown' 'S-1-5-32-578' = 'WellKnown' 'S-1-5-32-579' = 'WellKnown' 'S-1-5-32-580' = 'WellKnown' } } Process { if ($Identity) { foreach ($Ident in $Identity) { if ([Regex]::IsMatch($Ident, "^S-\d-\d+-(\d+-){1,14}\d+$")) { if ($Script:GlobalCacheSidConvert[$Ident]) { Write-Verbose "Convert-Identity - Processing SID $Ident from cache." if ($Script:GlobalCacheSidConvert[$Ident] -is [string]) { [PSCustomObject] @{Name = $Script:GlobalCacheSidConvert[$Ident] SID = $Ident Type = $wellKnownSIDs[$Ident] Error = '' } } else { $Script:GlobalCacheSidConvert[$Ident] } } else { Write-Verbose "Convert-Identity - Processing SID $Ident" try { [string] $Name = (([System.Security.Principal.SecurityIdentifier]::new($Ident)).Translate([System.Security.Principal.NTAccount])).Value $ErrorMessage = '' if ($Ident -like "S-1-5-21-*-519" -or $Ident -like "S-1-5-21-*-512") { $Type = 'Administrative' } elseif ($wellKnownSIDs[$Ident]) { $Type = $wellKnownSIDs[$Ident] } else { $Type = 'NotAdministrative' } } catch { [string] $Name = '' $ErrorMessage = $_.Exception.Message $Type = 'Unknown' } $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Name SID = $Ident Type = $Type Error = $ErrorMessage } $Script:GlobalCacheSidConvert[$Ident] } } else { Write-Verbose "Convert-Identity - Processing $Ident" if ($Script:GlobalCacheSidConvert[$Ident]) { $Script:GlobalCacheSidConvert[$Ident] } else { if ($Ident -like '*DC=*') { Write-Verbose "Convert-Identity - Processing DistinguishedName $Ident" try { $Object = [adsi]"LDAP://$($Ident)" if ($Object) { $SIDValue = [System.Security.Principal.SecurityIdentifier]::new($Object.objectSid.Value, 0).Value [string] $Name = (([System.Security.Principal.SecurityIdentifier]::new($SIDValue)).Translate([System.Security.Principal.NTAccount])).Value } else { [string] $Name = $Ident $SIDValue = $null } $ErrorMessage = '' if ($Ident -like "S-1-5-21-*-519" -or $Ident -like "S-1-5-21-*-512") { $Type = 'Administrative' } elseif ($wellKnownSIDs[$Ident]) { $Type = $wellKnownSIDs[$Ident] } else { $Type = 'NotAdministrative' } } catch { [string] $Name = $Ident $Type = 'Unknown' $ErrorMessage = $_.Exception.Message $SIDValue = $null } $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Name SID = $SIDValue Type = $Type Error = $ErrorMessage } $Script:GlobalCacheSidConvert[$Ident] } else { try { $SIDValue = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value if ($SIDValue -like "S-1-5-21-*-519" -or $SIDValue -like "S-1-5-21-*-512") { $Type = 'Administrative' } elseif ($wellKnownSIDs[$SIDValue]) { $Type = $wellKnownSIDs[$SIDValue] } else { $Type = 'NotAdministrative' } $ErrorMessage = '' } catch { $Type = 'Unknown' $ErrorMessage = $_.Exception.Message } $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Ident SID = $SIDValue Type = $Type Error = $ErrorMessage } $Script:GlobalCacheSidConvert[$Ident] } } } } } else { if ($SID) { foreach ($S in $SID) { if ($Script:GlobalCacheSidConvert[$S]) { $Script:GlobalCacheSidConvert[$S] } else { $Script:GlobalCacheSidConvert[$S] = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value $Script:GlobalCacheSidConvert[$S] } } } else { foreach ($Ident in $Name) { if ($Script:GlobalCacheSidConvert[$Ident]) { $Script:GlobalCacheSidConvert[$Ident] } else { $Script:GlobalCacheSidConvert[$Ident] = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value $Script:GlobalCacheSidConvert[$Ident] } } } } } End {} } function Convert-TimeToDays { [CmdletBinding()] param ($StartTime, $EndTime, [string] $Ignore = '*1601*') if ($null -ne $StartTime -and $null -ne $EndTime) { try { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start $StartTime -End $EndTime).Days } } catch {} } elseif ($null -ne $EndTime) { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start (Get-Date) -End ($EndTime)).Days } } elseif ($null -ne $StartTime) { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start $StartTime -End (Get-Date)).Days } } return $Days } function Convert-ToDateTime { [CmdletBinding()] param ([string] $Timestring, [string] $Ignore = '*1601*') Try { $DateTime = ([datetime]::FromFileTime($Timestring)) } catch { $DateTime = $null } if ($null -eq $DateTime -or $DateTime -like $Ignore) { return $null } else { return $DateTime } } function ConvertTo-OperatingSystem { <# .SYNOPSIS Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD .DESCRIPTION Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD .PARAMETER OperatingSystem Operating System as returned by Active Directory .PARAMETER OperatingSystemVersion Operating System Version as returned by Active Directory .EXAMPLE $Computers = Get-ADComputer -Filter * -Properties OperatingSystem, OperatingSystemVersion | ForEach-Object { $OPS = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion Add-Member -MemberType NoteProperty -Name 'OperatingSystemTranslated' -Value $OPS -InputObject $_ -Force $_ } $Computers | Select-Object DNS*, Name, SamAccountName, Enabled, OperatingSystem*, DistinguishedName | Format-Table .NOTES General notes #> [CmdletBinding()] param([string] $OperatingSystem, [string] $OperatingSystemVersion) if ($OperatingSystem -like '*Windows 10*') { $Systems = @{'10.0 (19041)' = 'Windows 10 2004' '10.0 (18363)' = "Windows 10 1909" '10.0 (18362)' = "Windows 10 1903" '10.0 (17763)' = "Windows 10 1809" '10.0 (17134)' = "Windows 10 1803" '10.0 (16299)' = "Windows 10 1709" '10.0 (15063)' = "Windows 10 1703" '10.0 (14393)' = "Windows 10 1607" '10.0 (10586)' = "Windows 10 1511" '10.0 (10240)' = "Windows 10 1507" '10.0 (18898)' = 'Windows 10 Insider Preview' '10.0.19041' = 'Windows 10 2004' '10.0.18363' = "Windows 10 1909" '10.0.18362' = "Windows 10 1903" '10.0.17763' = "Windows 10 1809" '10.0.17134' = "Windows 10 1803" '10.0.16299' = "Windows 10 1709" '10.0.15063' = "Windows 10 1703" '10.0.14393' = "Windows 10 1607" '10.0.10586' = "Windows 10 1511" '10.0.10240' = "Windows 10 1507" '10.0.18898' = 'Windows 10 Insider Preview' } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystem } } elseif ($OperatingSystem -like '*Windows Server*') { $Systems = @{'5.2 (3790)' = 'Windows Server 2003' '6.1 (7601)' = 'Windows Server 2008 R2' '10.0 (18362)' = "Windows Server, version 1903 (Semi-Annual Channel) 1903" '10.0 (17763)' = "Windows Server 2019 (Long-Term Servicing Channel) 1809" '10.0 (17134)' = "Windows Server, version 1803 (Semi-Annual Channel) 1803" '10.0 (14393)' = "Windows Server 2016 (Long-Term Servicing Channel) 1607" '10.0.18362' = "Windows Server, version 1903 (Semi-Annual Channel) 1903" '10.0.17763' = "Windows Server 2019 (Long-Term Servicing Channel) 1809" '10.0.17134' = "Windows Server, version 1803 (Semi-Annual Channel) 1803" '10.0.14393' = "Windows Server 2016 (Long-Term Servicing Channel) 1607" } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystem } } else { $System = $OperatingSystem } if ($System) { $System } else { 'Unknown' } } function Convert-UserAccountControl { [cmdletBinding()] param([alias('UAC')][int] $UserAccountControl, [string] $Separator) $UserAccount = [ordered] @{"SCRIPT" = 1 "ACCOUNTDISABLE" = 2 "HOMEDIR_REQUIRED" = 8 "LOCKOUT" = 16 "PASSWD_NOTREQD" = 32 "ENCRYPTED_TEXT_PWD_ALLOWED" = 128 "TEMP_DUPLICATE_ACCOUNT" = 256 "NORMAL_ACCOUNT" = 512 "INTERDOMAIN_TRUST_ACCOUNT" = 2048 "WORKSTATION_TRUST_ACCOUNT" = 4096 "SERVER_TRUST_ACCOUNT" = 8192 "DONT_EXPIRE_PASSWORD" = 65536 "MNS_LOGON_ACCOUNT" = 131072 "SMARTCARD_REQUIRED" = 262144 "TRUSTED_FOR_DELEGATION" = 524288 "NOT_DELEGATED" = 1048576 "USE_DES_KEY_ONLY" = 2097152 "DONT_REQ_PREAUTH" = 4194304 "PASSWORD_EXPIRED" = 8388608 "TRUSTED_TO_AUTH_FOR_DELEGATION" = 16777216 "PARTIAL_SECRETS_ACCOUNT" = 67108864 } $Output = foreach ($_ in $UserAccount.Keys) { $binaryAnd = $UserAccount[$_] -band $UserAccountControl if ($binaryAnd -ne "0") { $_ } } if ($Separator) { $Output -join $Separator } else { $Output } } function Get-ADADministrativeGroups { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Type Parameter description .PARAMETER Forest Parameter description .PARAMETER ExcludeDomains Parameter description .PARAMETER IncludeDomains Parameter description .PARAMETER ExtendedForestInformation Parameter description .EXAMPLE Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins Output (Where VALUE is Get-ADGroup output): Name Value ---- ----- ByNetBIOS {EVOTEC\Domain Admins, EVOTEC\Enterprise Admins, EVOTECPL\Domain Admins} ad.evotec.xyz {DomainAdmins, EnterpriseAdmins} ad.evotec.pl {DomainAdmins} .NOTES General notes #> [cmdletBinding()] param([parameter(Mandatory)][validateSet('DomainAdmins', 'EnterpriseAdmins')][string[]] $Type, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation) $ADDictionary = [ordered] @{} $ADDictionary['ByNetBIOS'] = [ordered] @{} $ADDictionary['BySID'] = [ordered] @{} $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $ADDictionary[$Domain] = [ordered] @{} $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] $DomainInformation = Get-ADDomain -Server $QueryServer if ($Type -contains 'DomainAdmins') { Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-512'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object { $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_ $ADDictionary[$Domain]['DomainAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)" $ADDictionary['BySID'][$_.SID.Value] = $_ } } } foreach ($Domain in $ForestInformation.Forest.Domains) { if (-not $ADDictionary[$Domain]) { $ADDictionary[$Domain] = [ordered] @{} } if ($Type -contains 'EnterpriseAdmins') { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] $DomainInformation = Get-ADDomain -Server $QueryServer Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-519'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object { $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_ $ADDictionary[$Domain]['EnterpriseAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)" $ADDictionary['BySID'][$_.SID.Value] = $_ } } } return $ADDictionary } function Get-ADTrustAttributes { [cmdletbinding()] Param([parameter(Mandatory = $false, ValueFromPipeline = $True)][int32]$Value) [String[]]$TrustAttributes = @(Foreach ($V in $Value) { if ([int32]$V -band 0x00000001) { "Non Transitive" } if ([int32]$V -band 0x00000002) { "UpLevel" } if ([int32]$V -band 0x00000004) { "Quarantaine (SID Filtering enabled)" } if ([int32]$V -band 0x00000008) { "Forest Transitive" } if ([int32]$V -band 0x00000010) { "Cross Organization (Selective Authentication enabled)" } if ([int32]$V -band 0x00000020) { "Within Forest" } if ([int32]$V -band 0x00000040) { "Treat as External" } if ([int32]$V -band 0x00000080) { "Uses RC4 Encryption" } }) return $TrustAttributes } function Get-CimData { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER ComputerName Parameter description .PARAMETER Protocol Parameter description .PARAMETER Class Parameter description .PARAMETER Properties Parameter description .EXAMPLE Get-CimData -Class 'win32_bios' -ComputerName AD1,EVOWIN Get-CimData -Class 'win32_bios' # Get-CimClass to get all classes .NOTES General notes #> [CmdletBinding()] param([string] $Class, [string] $NameSpace = 'root\cimv2', [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [string[]] $Properties = '*') $ExcludeProperties = 'CimClass', 'CimInstanceProperties', 'CimSystemProperties', 'SystemCreationClassName', 'CreationClassName' [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName $CimObject = @(# requires removal of this property for query [string[]] $PropertiesOnly = $Properties | Where-Object { $_ -ne 'PSComputerName' } $Computers = $ComputersSplit[1] if ($Computers.Count -gt 0) { if ($Protocol = 'Default') { Get-CimInstance -ClassName $Class -ComputerName $Computers -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace | 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 = $ComputersSplit[0] 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-FileOwner { [cmdletBinding()] param([Array] $Path, [switch] $Recursive, [switch] $JustPath, [switch] $Resolve, [switch] $AsHashTable) Begin {} Process { foreach ($P in $Path) { if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P } if ($FullPath -and (Test-Path -Path $FullPath)) { if ($JustPath) { $FullPath | ForEach-Object -Process { $ACL = Get-Acl -Path $_ $Object = [ordered]@{FullName = $_ Owner = $ACL.Owner } if ($Resolve) { $Identity = Convert-Identity -Identity $ACL.Owner if ($Identity) { $Object['OwnerName'] = $Identity.Name $Object['OwnerSid'] = $Identity.SID $Object['OwnerType'] = $Identity.Type } else { $Object['OwnerName'] = '' $Object['OwnerSid'] = '' $Object['OwnerType'] = '' } } if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } } } else { Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -Force | ForEach-Object -Process { $File = $_ $ACL = Get-Acl -Path $File.FullName $Object = [ordered] @{FullName = $_.FullName Extension = $_.Extension CreationTime = $_.CreationTime LastAccessTime = $_.LastAccessTime LastWriteTime = $_.LastWriteTime Attributes = $_.Attributes Owner = $ACL.Owner } if ($Resolve) { $Identity = Convert-Identity -Identity $ACL.Owner if ($Identity) { $Object['OwnerName'] = $Identity.Name $Object['OwnerSid'] = $Identity.SID $Object['OwnerType'] = $Identity.Type } else { $Object['OwnerName'] = '' $Object['OwnerSid'] = '' $Object['OwnerType'] = '' } } if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } } } } } } End {} } function Get-FilePermission { [alias('Get-PSPermissions', 'Get-FilePermissions')] [cmdletBinding()] param([Array] $Path, [switch] $Inherited, [switch] $NotInherited, [switch] $ResolveTypes, [switch] $Extended, [switch] $IncludeACLObject, [switch] $AsHashTable) foreach ($P in $Path) { if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P } $TestPath = Test-Path -Path $FullPath if ($TestPath) { $ACLS = (Get-Acl -Path $FullPath) $Output = foreach ($ACL in $ACLS.Access) { if ($Inherited) { if ($ACL.IsInherited -eq $false) { continue } } if ($NotInherited) { if ($ACL.IsInherited -eq $true) { continue } } $TranslateRights = Convert-GenericRightsToFileSystemRights -OriginalRights $ACL.FileSystemRights $ReturnObject = [ordered] @{} $ReturnObject['Path' ] = $FullPath $ReturnObject['AccessControlType'] = $ACL.AccessControlType if ($ResolveTypes) { $Identity = Convert-Identity -Identity $ACL.IdentityReference if ($Identity) { $ReturnObject['Principal'] = $ACL.IdentityReference $ReturnObject['PrincipalName'] = $Identity.Name $ReturnObject['PrincipalSid'] = $Identity.Sid $ReturnObject['PrincipalType'] = $Identity.Type } else { $ReturnObject['Principal'] = $Identity $ReturnObject['PrincipalName'] = '' $ReturnObject['PrincipalSid'] = '' $ReturnObject['PrincipalType'] = '' } } else { $ReturnObject['Principal'] = $ACL.IdentityReference.Value } $ReturnObject['FileSystemRights'] = $TranslateRights $ReturnObject['IsInherited'] = $ACL.IsInherited if ($Extended) { $ReturnObject['InheritanceFlags'] = $ACL.InheritanceFlags $ReturnObject['PropagationFlags'] = $ACL.PropagationFlags } if ($IncludeACLObject) { $ReturnObject['ACL'] = $ACL $ReturnObject['AllACL'] = $ACLS } if ($AsHashTable) { $ReturnObject } else { [PSCustomObject] $ReturnObject } } $Output } else { Write-Warning "Get-PSPermissions - Path $Path doesn't exists. Skipping." } } } function Get-PSRegistry { [cmdletbinding()] param([alias('Path')][string[]] $RegistryPath, [string[]] $ComputerName = $Env:COMPUTERNAME) $RootKeyDictionary = @{HKEY_CLASSES_ROOT = 2147483648 HKCR = 2147483648 HKEY_CURRENT_USER = 2147483649 HKCU = 2147483649 HKEY_LOCAL_MACHINE = 2147483650 HKLM = 2147483650 HKEY_USERS = 2147483651 HKU = 2147483651 HKEY_CURRENT_CONFIG = 2147483653 HKCC = 2147483653 HKEY_DYN_DATA = 2147483654 HKDD = 2147483654 } $TypesDictionary = @{'1' = 'GetStringValue' '2' = 'GetExpandedStringValue' '3' = 'GetBinaryValue' '4' = 'GetDWORDValue' '7' = 'GetMultiStringValue' '11' = 'GetQWORDValue' } $Dictionary = @{'HKCR:' = 'HKEY_CLASSES_ROOT' 'HKCU:' = 'HKEY_CURRENT_USER' 'HKLM:' = 'HKEY_LOCAL_MACHINE' 'HKU:' = 'HKEY_USERS' 'HKCC:' = 'HKEY_CURRENT_CONFIG' 'HKDD:' = 'HKEY_DYN_DATA' } [uint32] $RootKey = $null [Array] $Computers = Get-ComputerSplit -ComputerName $ComputerName foreach ($Registry in $RegistryPath) { If ($Registry -like '*:*') { foreach ($Key in $Dictionary.Keys) { if ($Registry.StartsWith($Key)) { $Registry = $Registry -replace $Key, $Dictionary[$Key] break } } } for ($ComputerSplit = 0; $ComputerSplit -lt $Computers.Count; $ComputerSplit++) { if ($Computers[$ComputerSplit].Count -gt 0) { $Arguments = foreach ($_ in $RootKeyDictionary.Keys) { if ($Registry.StartsWith($_)) { $RootKey = [uint32] $RootKeyDictionary[$_] @{hDefKey = [uint32] $RootKeyDictionary[$_] sSubKeyName = $Registry.substring($_.Length + 1) } break } } if ($ComputerSplit -eq 0) { $Output2 = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumValues -Arguments $Arguments -Verbose:$false $OutputKeys = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumKey -Arguments $Arguments -Verbose:$false } else { $Output2 = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumValues -Arguments $Arguments -ComputerName $Computers[$ComputerSplit] -Verbose:$false $OutputKeys = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumKey -ComputerName $Computers[$ComputerSplit] -Arguments $Arguments -Verbose:$false } foreach ($Entry in $Output2) { $RegistryOutput = [ordered] @{} if ($Entry.ReturnValue -ne 0) { $RegistryOutput['PSError'] = $true } else { $RegistryOutput['PSError'] = $false $Types = $Entry.Types $Names = $Entry.sNames for ($i = 0; $i -lt $Names.Count; $i++) { $Arguments['sValueName'] = $Names[$i] $MethodName = $TypesDictionary["$($Types[$i])"] if ($ComputerSplit -eq 0) { $Values = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -Verbose:$false } else { $Values = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -ComputerName $Entry.PSComputerName -Verbose:$false } if ($null -ne $Values.sValue) { if ($Names[$i]) { $RegistryOutput[$Names[$i]] = $Values.sValue } else { $RegistryOutput['DefaultKey'] = $Values.sValue } } elseif ($null -ne $Values.uValue) { if ($Names[$i]) { $RegistryOutput[$Names[$i]] = $Values.uValue } else { $RegistryOutput['DefaultKey'] = $Values.sValue } } } } if (-not $RegistryOutput['PSComputerName']) { if ($ComputerSplit -eq 0) { $RegistryOutput['PSComputerName'] = $ENV:COMPUTERNAME } else { $RegistryOutput['PSComputerName'] = $Entry.PSComputerName } } else { if ($ComputerSplit -eq 0) { $RegistryOutput['ComputerName'] = $ENV:COMPUTERNAME } else { $RegistryOutput['ComputerName'] = $Entry.PSComputerName } } if (-not $RegistryOutput['PSSubKeys']) { $RegistryOutput['PSSubKeys'] = $OutputKeys.sNames } else { $RegistryOutput['SubKeys'] = $OutputKeys.sNames } $RegistryOutput['PSPath'] = $Registry [PSCustomObject] $RegistryOutput } } } } } function Get-WinADForestDetails { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [string] $Filter = '*', [switch] $TestAvailability, [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All', [int[]] $Ports = 135, [int] $PortsTimeout = 100, [int] $PingCount = 1, [switch] $Extended, [System.Collections.IDictionary] $ExtendedForestInformation) if ($Global:ProgressPreference -ne 'SilentlyContinue') { $TemporaryProgress = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' } if (-not $ExtendedForestInformation) { $Findings = [ordered] @{} try { if ($Forest) { $ForestInformation = Get-ADForest -ErrorAction Stop -Identity $Forest } else { $ForestInformation = Get-ADForest -ErrorAction Stop } } catch { Write-Warning "Get-WinADForestDetails - Error discovering DC for Forest - $($_.Exception.Message)" return } if (-not $ForestInformation) { return } $Findings['Forest'] = $ForestInformation $Findings['ForestDomainControllers'] = @() $Findings['QueryServers'] = @{} $Findings['DomainDomainControllers'] = @{} [Array] $Findings['Domains'] = foreach ($_ in $ForestInformation.Domains) { if ($IncludeDomains) { if ($_ -in $IncludeDomains) { $_.ToLower() } continue } if ($_ -notin $ExcludeDomains) { $_.ToLower() } } foreach ($Domain in $ForestInformation.Domains) { try { $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop $OrderedDC = [ordered] @{Domain = $DC.Domain Forest = $DC.Forest HostName = [Array] $DC.HostName IPv4Address = $DC.IPv4Address IPv6Address = $DC.IPv6Address Name = $DC.Name Site = $DC.Site } } catch { Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)" continue } if ($Domain -eq $Findings['Forest']['Name']) { $Findings['QueryServers']['Forest'] = $OrderedDC } $Findings['QueryServers']["$Domain"] = $OrderedDC } [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) { $QueryServer = $Findings['QueryServers'][$Domain]['HostName'][0] [Array] $AllDC = try { try { $DomainControllers = Get-ADDomainController -Filter $Filter -Server $QueryServer -ErrorAction Stop } catch { Write-Warning "Get-WinADForestDetails - Error listing DCs for domain $Domain - $($_.Exception.Message)" continue } foreach ($S in $DomainControllers) { if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } } if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } } $Server = [ordered] @{Domain = $Domain HostName = $S.HostName Name = $S.Name Forest = $ForestInformation.RootDomain Site = $S.Site IPV4Address = $S.IPV4Address IPV6Address = $S.IPV6Address IsGlobalCatalog = $S.IsGlobalCatalog IsReadOnly = $S.IsReadOnly IsSchemaMaster = ($S.OperationMasterRoles -contains 'SchemaMaster') IsDomainNamingMaster = ($S.OperationMasterRoles -contains 'DomainNamingMaster') IsPDC = ($S.OperationMasterRoles -contains 'PDCEmulator') IsRIDMaster = ($S.OperationMasterRoles -contains 'RIDMaster') IsInfrastructureMaster = ($S.OperationMasterRoles -contains 'InfrastructureMaster') OperatingSystem = $S.OperatingSystem OperatingSystemVersion = $S.OperatingSystemVersion OperatingSystemLong = ConvertTo-OperatingSystem -OperatingSystem $S.OperatingSystem -OperatingSystemVersion $S.OperatingSystemVersion LdapPort = $S.LdapPort SslPort = $S.SslPort DistinguishedName = $S.ComputerObjectDN Pingable = $null WinRM = $null PortOpen = $null Comment = '' } if ($TestAvailability) { if ($Test -eq 'All' -or $Test -like 'Ping*') { $Server.Pingable = Test-Connection -ComputerName $Server.IPV4Address -Quiet -Count $PingCount } if ($Test -eq 'All' -or $Test -like '*WinRM*') { $Server.WinRM = (Test-WinRM -ComputerName $Server.HostName).Status } if ($Test -eq 'All' -or '*PortOpen*') { $Server.PortOpen = (Test-ComputerPort -Server $Server.HostName -PortTCP $Ports -Timeout $PortsTimeout).Status } } [PSCustomObject] $Server } } catch { [PSCustomObject]@{Domain = $Domain HostName = '' Name = '' Forest = $ForestInformation.RootDomain IPV4Address = '' IPV6Address = '' IsGlobalCatalog = '' IsReadOnly = '' Site = '' SchemaMaster = $false DomainNamingMasterMaster = $false PDCEmulator = $false RIDMaster = $false InfrastructureMaster = $false LdapPort = '' SslPort = '' DistinguishedName = '' Pingable = $null WinRM = $null PortOpen = $null Comment = $_.Exception.Message -replace "`n", " " -replace "`r", " " } } if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC } [Array] $Findings['DomainDomainControllers'][$Domain] } if ($Extended) { $Findings['DomainsExtended'] = @{} $Findings['DomainsExtendedNetBIOS'] = @{} foreach ($DomainEx in $Findings['Domains']) { try { $Findings['DomainsExtended'][$DomainEx] = Get-ADDomain -Server $Findings['QueryServers'][$DomainEx].HostName[0] | ForEach-Object { [ordered] @{AllowedDNSSuffixes = $_.AllowedDNSSuffixes | ForEach-Object -Process { $_ } ChildDomains = $_.ChildDomains | ForEach-Object -Process { $_ } ComputersContainer = $_.ComputersContainer DeletedObjectsContainer = $_.DeletedObjectsContainer DistinguishedName = $_.DistinguishedName DNSRoot = $_.DNSRoot DomainControllersContainer = $_.DomainControllersContainer DomainMode = $_.DomainMode DomainSID = $_.DomainSID.Value ForeignSecurityPrincipalsContainer = $_.ForeignSecurityPrincipalsContainer Forest = $_.Forest InfrastructureMaster = $_.InfrastructureMaster LastLogonReplicationInterval = $_.LastLogonReplicationInterval LinkedGroupPolicyObjects = $_.LinkedGroupPolicyObjects | ForEach-Object -Process { $_ } LostAndFoundContainer = $_.LostAndFoundContainer ManagedBy = $_.ManagedBy Name = $_.Name NetBIOSName = $_.NetBIOSName ObjectClass = $_.ObjectClass ObjectGUID = $_.ObjectGUID ParentDomain = $_.ParentDomain PDCEmulator = $_.PDCEmulator PublicKeyRequiredPasswordRolling = $_.PublicKeyRequiredPasswordRolling | ForEach-Object -Process { $_ } QuotasContainer = $_.QuotasContainer ReadOnlyReplicaDirectoryServers = $_.ReadOnlyReplicaDirectoryServers | ForEach-Object -Process { $_ } ReplicaDirectoryServers = $_.ReplicaDirectoryServers | ForEach-Object -Process { $_ } RIDMaster = $_.RIDMaster SubordinateReferences = $_.SubordinateReferences | ForEach-Object -Process { $_ } SystemsContainer = $_.SystemsContainer UsersContainer = $_.UsersContainer } } $NetBios = $Findings['DomainsExtended'][$DomainEx]['NetBIOSName'] $Findings['DomainsExtendedNetBIOS'][$NetBios] = $Findings['DomainsExtended'][$DomainEx] } catch { Write-Warning "Get-WinADForestDetails - Error gathering Domain Information for domain $DomainEx - $($_.Exception.Message)" continue } } } if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } $Findings } else { $Findings = Copy-DictionaryManual -Dictionary $ExtendedForestInformation [Array] $Findings['Domains'] = foreach ($_ in $Findings.Domains) { if ($IncludeDomains) { if ($_ -in $IncludeDomains) { $_.ToLower() } continue } if ($_ -notin $ExcludeDomains) { $_.ToLower() } } foreach ($_ in [string[]] $Findings.DomainDomainControllers.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainDomainControllers.Remove($_) } } foreach ($_ in [string[]] $Findings.DomainsExtended.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainsExtended.Remove($_) $NetBiosName = $Findings.DomainsExtended.$_.'NetBIOSName' if ($NetBiosName) { $Findings.DomainsExtendedNetBIOS.Remove($NetBiosName) } } } [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) { [Array] $AllDC = foreach ($S in $Findings.DomainDomainControllers["$Domain"]) { if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } } if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } } $S } if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC } [Array] $Findings['DomainDomainControllers'][$Domain] } $Findings } } function Get-WinADForestGUIDs { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Domain Parameter description .PARAMETER RootDSE Parameter description .PARAMETER DisplayNameKey Parameter description .EXAMPLE Get-WinADForestGUIDs .EXAMPLE Get-WinADForestGUIDs -DisplayNameKey .NOTES General notes #> [alias('Get-WinADDomainGUIDs')] [cmdletbinding()] param([string] $Domain = $Env:USERDNSDOMAIN, [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE, [switch] $DisplayNameKey) if ($null -eq $RootDSE) { $RootDSE = Get-ADRootDSE -Server $Domain } $GUID = @{} $GUID.Add('00000000-0000-0000-0000-000000000000', 'All') $Schema = Get-ADObject -SearchBase $RootDSE.schemaNamingContext -LDAPFilter '(schemaIDGUID=*)' -Properties name, schemaIDGUID foreach ($S in $Schema) { if ($DisplayNameKey) { $GUID["$($S.name)"] = $(([System.GUID]$S.schemaIDGUID).Guid) } else { $GUID["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.name } } $Extended = Get-ADObject -SearchBase "CN=Extended-Rights,$($RootDSE.configurationNamingContext)" -LDAPFilter '(objectClass=controlAccessRight)' -Properties name, rightsGUID foreach ($S in $Extended) { if ($DisplayNameKey) { $GUID["$($S.name)"] = $(([System.GUID]$S.rightsGUID).Guid) } else { $GUID["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.name } } return $GUID } function Rename-LatinCharacters { [alias('Remove-StringLatinCharacters')] param ([string]$String) [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($String)) } function Set-FileOwner { [cmdletBinding(SupportsShouldProcess)] param([Array] $Path, [switch] $Recursive, [string] $Owner, [string[]] $Exlude, [switch] $JustPath) Begin {} Process { foreach ($P in $Path) { if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P } $OwnerTranslated = [System.Security.Principal.NTAccount]::new($Owner) if ($FullPath -and (Test-Path -Path $FullPath)) { if ($JustPath) { $FullPath | ForEach-Object -Process { $File = $_ $ACL = Get-Acl -Path $File if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) { if ($PSCmdlet.ShouldProcess($File, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) { try { $ACL.SetOwner($OwnerTranslated) Set-Acl -Path $File -AclObject $ACL } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" } } } } } else { Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive | ForEach-Object -Process { $File = $_ $ACL = Get-Acl -Path $File.FullName if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) { if ($PSCmdlet.ShouldProcess($File.FullName, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) { try { $ACL.SetOwner($OwnerTranslated) Set-Acl -Path $File.FullName -AclObject $ACL } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" } } } } } } } } End {} } function Set-PSRegistry { [cmdletbinding()] param([string[]] $ComputerName = $Env:COMPUTERNAME, [Parameter(Mandatory)][string] $RegistryPath, [Parameter(Mandatory)][ValidateSet('REG_SZ', 'REG_EXPAND_SZ', 'REG_BINARY', 'REG_DWORD', 'REG_MULTI_SZ', 'REG_QWORD')][string] $Type, [Parameter(Mandatory)][string] $Key, [Parameter(Mandatory)][object] $Value) [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName [uint32] $RootKey = $null $RootKeyDictionary = @{HKEY_CLASSES_ROOT = 2147483648 HKCR = 2147483648 HKEY_CURRENT_USER = 2147483649 HKCU = 2147483649 HKEY_LOCAL_MACHINE = 2147483650 HKLM = 2147483650 HKEY_USERS = 2147483651 HKU = 2147483651 HKEY_CURRENT_CONFIG = 2147483653 HKCC = 2147483653 HKEY_DYN_DATA = 2147483654 HKDD = 2147483654 } $TypesDictionary = @{'REG_SZ' = 'SetStringValue' 'REG_EXPAND_SZ' = 'SetExpandedStringValue' 'REG_BINARY' = 'SetBinaryValue' 'REG_DWORD' = 'SetDWORDValue' 'REG_MULTI_SZ' = 'SetMultiStringValue' 'REG_QWORD' = 'SetQWORDValue' } $MethodName = $TypesDictionary["$($Type)"] $Arguments = foreach ($_ in $RootKeyDictionary.Keys) { if ($RegistryPath.StartsWith($_)) { $RootKey = [uint32] $RootKeyDictionary[$_] $RegistryValue = @{hDefKey = [uint32] $RootKeyDictionary[$_] sSubKeyName = $RegistryPath.substring($_.Length + 1) sValueName = $Key } if ($Type -in ('REG_SZ', 'REG_EXPAND_SZ', 'REG_MULTI_SZ')) { $RegistryValue['sValue'] = [string] $Value } elseif ($Type -in ('REG_DWORD', 'REG_QWORD')) { $RegistryValue['uValue'] = [uint32] $Value } elseif ($Type -in ('REG_BINARY')) { $RegistryValue['uValue'] = [uint8] $Value } $RegistryValue break } } foreach ($Computer in $ComputersSplit[0]) { try { $ReturnValues = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -ErrorAction Stop -Verbose:$false if ($ReturnValues.ReturnValue -ne 0) { Write-Warning "Set-PSRegistry - Setting registry to $RegistryPath on $Computer may have failed. Please verify." } } catch { Write-Warning "Set-PSRegistry - Setting registry to $RegistryPath on $Computer have failed. Error: $($_.Exception.Message)" } } foreach ($Computer in $ComputersSplit[1]) { try { $ReturnValues = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -ComputerName $Computer -ErrorAction Stop -Verbose:$false if ($ReturnValues.ReturnValue -ne 0) { Write-Warning "Set-PSRegistry - Setting registry to $RegistryPath on $Computer may have failed. Please verify." } } catch { Write-Warning "Set-PSRegistry - Setting registry to $RegistryPath on $Computer have failed. Error: $($_.Exception.Message)" } } } function Convert-GenericRightsToFileSystemRights { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER OriginalRights Parameter description .EXAMPLE An example .NOTES .LINK Improved https://blog.cjwdev.co.uk/2011/06/28/permissions-not-included-in-net-accessrule-filesystemrights-enum/ #> [cmdletBinding()] param([System.Security.AccessControl.FileSystemRights] $OriginalRights) Begin { $FileSystemRights = [System.Security.AccessControl.FileSystemRights] $GenericRights = @{GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 GENERIC_EXECUTE = 0x20000000 GENERIC_ALL = 0x10000000 FILTER_GENERIC = 0x0FFFFFFF } $MappedGenericRights = @{FILE_GENERIC_EXECUTE = $FileSystemRights::ExecuteFile -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::ReadAttributes -bor $FileSystemRights::Synchronize FILE_GENERIC_READ = $FileSystemRights::ReadAttributes -bor $FileSystemRights::ReadData -bor $FileSystemRights::ReadExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize FILE_GENERIC_WRITE = $FileSystemRights::AppendData -bor $FileSystemRights::WriteAttributes -bor $FileSystemRights::WriteData -bor $FileSystemRights::WriteExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize FILE_GENERIC_ALL = $FileSystemRights::FullControl } } Process { $MappedRights = [System.Security.AccessControl.FileSystemRights]::new() if ($OriginalRights -band $GenericRights.GENERIC_EXECUTE) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_EXECUTE } if ($OriginalRights -band $GenericRights.GENERIC_READ) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_READ } if ($OriginalRights -band $GenericRights.GENERIC_WRITE) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_WRITE } if ($OriginalRights -band $GenericRights.GENERIC_ALL) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_ALL } (($OriginalRights -bAND $GenericRights.FILTER_GENERIC) -bOR $MappedRights) -as $FileSystemRights } End {} } function Copy-DictionaryManual { [CmdletBinding()] param([System.Collections.IDictionary] $Dictionary) $clone = @{} foreach ($Key in $Dictionary.Keys) { $value = $Dictionary.$Key $clonedValue = switch ($Dictionary.$Key) { { $null -eq $_ } { $null continue } { $_ -is [System.Collections.IDictionary] } { Copy-DictionaryManual -Dictionary $_ continue } { $type = $_.GetType() $type.IsPrimitive -or $type.IsValueType -or $_ -is [string] } { $_ continue } default { $_ | Select-Object -Property * } } if ($value -is [System.Collections.IList]) { $clone[$Key] = @($clonedValue) } else { $clone[$Key] = $clonedValue } } $clone } function Get-ComputerSplit { [CmdletBinding()] param([string[]] $ComputerName) if ($null -eq $ComputerName) { $ComputerName = $Env:COMPUTERNAME } try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Env:COMPUTERNAME } $ComputersLocal = $null [Array] $Computers = foreach ($_ in $ComputerName) { if ($_ -eq '' -or $null -eq $_) { $_ = $Env:COMPUTERNAME } if ($_ -ne $Env:COMPUTERNAME -and $_ -ne $LocalComputerDNSName) { $_ } else { $ComputersLocal = $_ } } , @($ComputersLocal, $Computers) } function Test-ComputerPort { [CmdletBinding()] param ([alias('Server')][string[]] $ComputerName, [int[]] $PortTCP, [int[]] $PortUDP, [int]$Timeout = 5000) begin { if ($Global:ProgressPreference -ne 'SilentlyContinue') { $TemporaryProgress = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' } } process { foreach ($Computer in $ComputerName) { foreach ($P in $PortTCP) { $Output = [ordered] @{'ComputerName' = $Computer 'Port' = $P 'Protocol' = 'TCP' 'Status' = $null 'Summary' = $null 'Response' = $null } $TcpClient = Test-NetConnection -ComputerName $Computer -Port $P -InformationLevel Detailed -WarningAction SilentlyContinue if ($TcpClient.TcpTestSucceeded) { $Output['Status'] = $TcpClient.TcpTestSucceeded $Output['Summary'] = "TCP $P Successful" } else { $Output['Status'] = $false $Output['Summary'] = "TCP $P Failed" $Output['Response'] = $Warnings } [PSCustomObject]$Output } foreach ($P in $PortUDP) { $Output = [ordered] @{'ComputerName' = $Computer 'Port' = $P 'Protocol' = 'UDP' 'Status' = $null 'Summary' = $null } $UdpClient = [System.Net.Sockets.UdpClient]::new($Computer, $P) $UdpClient.Client.ReceiveTimeout = $Timeout $Encoding = [System.Text.ASCIIEncoding]::new() $byte = $Encoding.GetBytes("Evotec") [void]$UdpClient.Send($byte, $byte.length) $RemoteEndpoint = [System.Net.IPEndPoint]::new([System.Net.IPAddress]::Any, 0) try { $Bytes = $UdpClient.Receive([ref]$RemoteEndpoint) [string]$Data = $Encoding.GetString($Bytes) If ($Data) { $Output['Status'] = $true $Output['Summary'] = "UDP $P Successful" $Output['Response'] = $Data } } catch { $Output['Status'] = $false $Output['Summary'] = "UDP $P Failed" $Output['Response'] = $_.Exception.Message } $UdpClient.Close() $UdpClient.Dispose() [PSCustomObject]$Output } } } end { if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } } } function Test-WinRM { [CmdletBinding()] param ([alias('Server')][string[]] $ComputerName) $Output = foreach ($Computer in $ComputerName) { $Test = [PSCustomObject] @{Output = $null Status = $null ComputerName = $Computer } try { $Test.Output = Test-WSMan -ComputerName $Computer -ErrorAction Stop $Test.Status = $true } catch { $Test.Status = $false } $Test } $Output } function Get-WinADCache { [alias('Get-ADCache')] [cmdletbinding()] param([switch] $ByDN, [switch] $ByNetBiosName) $ForestObjectsCache = [ordered] @{} $Forest = Get-ADForest foreach ($Domain in $Forest.Domains) { $Server = Get-ADDomainController -Discover -DomainName $Domain try { $DomainInformation = Get-ADDomain -Server $Server.Hostname[0] $Users = Get-ADUser -Filter * -Server $Server.Hostname[0] $Groups = Get-ADGroup -Filter * -Server $Server.Hostname[0] $Computers = Get-ADComputer -Filter * -Server $Server.Hostname[0] } catch { Write-Warning "Get-ADCache - Can't process domain $Domain - $($_.Exception.Message)" continue } if ($ByDN) { foreach ($_ in $Users) { $ForestObjectsCache["$($_.DistinguishedName)"] = $_ } foreach ($_ in $Groups) { $ForestObjectsCache["$($_.DistinguishedName)"] = $_ } foreach ($_ in $Computers) { $ForestObjectsCache["$($_.DistinguishedName)"] = $_ } } elseif ($ByNetBiosName) { foreach ($_ in $Users) { $Identity = -join ($DomainInformation.NetBIOSName, '\', $($_.SamAccountName)) $ForestObjectsCache["$Identity"] = $_ } foreach ($_ in $Groups) { $Identity = -join ($DomainInformation.NetBIOSName, '\', $($_.SamAccountName)) $ForestObjectsCache["$Identity"] = $_ } foreach ($_ in $Computers) { $Identity = -join ($DomainInformation.NetBIOSName, '\', $($_.SamAccountName)) $ForestObjectsCache["$Identity"] = $_ } } else { Write-Warning "Get-ADCache - No choice made." } } $ForestObjectsCache } function Get-WinADDomainOrganizationalUnitsACLExtended { [cmdletbinding()] param([Array] $DomainOrganizationalUnitsClean, [string] $Domain = $Env:USERDNSDOMAIN, [string] $NetBiosName, [string] $RootDomainNamingContext, [System.Collections.IDictionary] $GUID, [System.Collections.IDictionary] $ForestObjectsCache, $Server) if (-not $GUID) { $GUID = @{} } if (-not $ForestObjectsCache) { $ForestObjectsCache = @{} } $OUs = @(foreach ($OU in $DomainOrganizationalUnitsClean) { @{Name = 'Organizational Unit'; Value = $OU.DistinguishedName } }) if ($Server) { $null = New-PSDrive -Name $NetBiosName -Root '' -PSProvider ActiveDirectory -Server $Server } else { $null = New-PSDrive -Name $NetBiosName -Root '' -PSProvider ActiveDirectory -Server $Domain } foreach ($OU in $OUs) { $ACLs = Get-Acl -Path "$NetBiosName`:\$($OU.Value)" | Select-Object -ExpandProperty Access foreach ($ACL in $ACLs) { if ($ACL.IdentityReference -like '*\*') { $TemporaryIdentity = $ForestObjectsCache["$($ACL.IdentityReference)"] $IdentityReferenceType = $TemporaryIdentity.ObjectClass $IdentityReference = $ACL.IdentityReference.Value } elseif ($ACL.IdentityReference -like '*-*-*-*') { $ConvertedSID = ConvertFrom-SID -SID $ACL.IdentityReference $TemporaryIdentity = $ForestObjectsCache["$($ConvertedSID.Name)"] $IdentityReferenceType = $TemporaryIdentity.ObjectClass $IdentityReference = $ConvertedSID.Name } else { $IdentityReference = $ACL.IdentityReference $IdentityReferenceType = 'Unknown' } [PSCustomObject] @{'Distinguished Name' = $OU.Value 'Type' = $OU.Name 'AccessControlType' = $ACL.AccessControlType 'Rights' = $Global:Rights["$($ACL.ActiveDirectoryRights)"]["$($ACL.ObjectFlags)"] 'ObjectType Name' = $GUID["$($ACL.objectType)"] 'Inherited ObjectType Name' = $GUID["$($ACL.inheritedObjectType)"] 'ActiveDirectoryRights' = $ACL.ActiveDirectoryRights 'InheritanceType' = $ACL.InheritanceType 'ObjectFlags' = $ACL.ObjectFlags 'IdentityReference' = $IdentityReference 'IdentityReferenceType' = $IdentityReferenceType 'IsInherited' = $ACL.IsInherited 'InheritanceFlags' = $ACL.InheritanceFlags 'PropagationFlags' = $ACL.PropagationFlags } } } } $Script:Rights = @{"Self" = @{"InheritedObjectAceTypePresent" = "" "ObjectAceTypePresent" = "" "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "" 'None' = "" } "DeleteChild, DeleteTree, Delete" = @{"InheritedObjectAceTypePresent" = "DeleteChild, DeleteTree, Delete" "ObjectAceTypePresent" = "DeleteChild, DeleteTree, Delete" "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "DeleteChild, DeleteTree, Delete" 'None' = "DeleteChild, DeleteTree, Delete" } "GenericRead" = @{"InheritedObjectAceTypePresent" = "Read Permissions,List Contents,Read All Properties,List" "ObjectAceTypePresent" = "Read Permissions,List Contents,Read All Properties,List" "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Read Permissions,List Contents,Read All Properties,List" 'None' = "Read Permissions,List Contents,Read All Properties,List" } "CreateChild" = @{"InheritedObjectAceTypePresent" = "Create" "ObjectAceTypePresent" = "Create" "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Create" 'None' = "Create" } "DeleteChild" = @{"InheritedObjectAceTypePresent" = "Delete" "ObjectAceTypePresent" = "Delete" "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Delete" 'None' = "Delete" } "GenericAll" = @{"InheritedObjectAceTypePresent" = "Full Control" "ObjectAceTypePresent" = "Full Control" "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Full Control" 'None' = "Full Control" } "CreateChild, DeleteChild" = @{"InheritedObjectAceTypePresent" = "Create/Delete" "ObjectAceTypePresent" = "Create/Delete" "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Create/Delete" 'None' = "Create/Delete" } "ReadProperty, WriteProperty" = @{"InheritedObjectAceTypePresent" = "Read All Properties;Write All Properties" "ObjectAceTypePresent" = "Read All Properties;Write All Properties" "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Read All Properties;Write All Properties" 'None' = "Read All Properties;Write All Properties" } "WriteProperty" = @{"InheritedObjectAceTypePresent" = "Write All Properties" "ObjectAceTypePresent" = "Write" "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Write" 'None' = "Write All Properties" } "ReadProperty" = @{"InheritedObjectAceTypePresent" = "Read All Properties" "ObjectAceTypePresent" = "Read" "ObjectAceTypePresent, InheritedObjectAceTypePresent" = "Read" 'None' = "Read All Properties" } } function New-ADForestDrives { [cmdletbinding()] param([string] $ForestName, [string] $ObjectDN) if (-not $Global:ADDrivesMapped) { if ($ForestName) { $Forest = Get-ADForest -Identity $ForestName } else { $Forest = Get-ADForest } if ($ObjectDN) { $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $ObjectDN -ToDC) -replace '=' -replace ',' if (-not(Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { try { if ($Server) { $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Server.Hostname[0] -Scope Global -WhatIf:$false Write-Verbose "New-ADForestDrives - Mapped drive $Domain / $($Server.Hostname[0])" } else { $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Domain -Scope Global -WhatIf:$false } } catch { Write-Warning "New-ADForestDrives - Couldn't map new AD psdrive for $Domain / $($Server.Hostname[0])" } } } else { foreach ($Domain in $Forest.Domains) { try { $Server = Get-ADDomainController -Discover -DomainName $Domain $DomainInformation = Get-ADDomain -Server $Server.Hostname[0] } catch { Write-Warning "New-ADForestDrives - Can't process domain $Domain - $($_.Exception.Message)" continue } $ObjectDN = $DomainInformation.DistinguishedName $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $ObjectDN -ToDC) -replace '=' -replace ',' if (-not(Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { try { if ($Server) { $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Server.Hostname[0] -Scope Global -WhatIf:$false Write-Verbose "New-ADForestDrives - Mapped drive $Domain / $Server" } else { $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Domain -Scope Global -WhatIf:$false } } catch { Write-Warning "New-ADForestDrives - Couldn't map new AD psdrive for $Domain / $Server $($_.Exception.Message)" } } } } $Global:ADDrivesMapped = $true } } Function Set-WinADGPOMissingPermissions { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Domain Parameter description .EXAMPLE An example .NOTES Based on https://secureinfra.blog/2018/12/31/most-common-mistakes-in-active-directory-and-domain-services-part-1/ #> [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $SkipRODC, [System.Collections.IDictionary] $ExtendedForestInformation, [validateset('AuthenticatedUsers', 'DomainComputers', 'Either')][string] $Mode = 'Either') if (-not $ExtendedForestInformation) { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC } else { $ForestInformation = $ExtendedForestInformation } foreach ($Domain in $ForestInformation.Domains) { $DomainInformation = Get-ADDomain -Server $QueryServer $DomainComputersSID = $('{0}-515' -f $DomainInformation.DomainSID.Value) $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $GPOs = Get-GPO -All -Domain $Domain -Server $QueryServer $MissingPermissions = @(foreach ($GPO in $GPOs) { $Permissions = Get-GPPermission -Guid $GPO.Id -All -Server $QueryServer -DomainName $Domain | Select-Object -ExpandProperty Trustee if ($Mode -eq 'Either') { if ($Permissions.Sid.Value -notcontains 'S-1-5-11' -and $Permissions.Sid.Value -notcontains $DomainComputersSID) { $GPO } } elseif ($Mode -eq 'AuthenticatedUsers') { if ($Permissions.Sid.Value -notcontains 'S-1-5-11') { $GPO } } elseif ($Mode -eq 'DomainComputers') { if ($Permissions.Sid.Value -notcontains $DomainComputersSID) { $GPO } } }) $MissingPermissions } } 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 Add-ADACL { [cmdletBinding(SupportsShouldProcess)] param([Parameter(Mandatory)][Array] $ACL, [Parameter(Mandatory)][string] $Principal, [Parameter(Mandatory)][System.DirectoryServices.ActiveDirectoryRights] $AccessRule, [Parameter(Mandatory)][System.Security.AccessControl.AccessControlType] $AccessControlType) if ($Principal -is [string]) { if ($Principal -like '*/*') { $SplittedName = $Principal -split '/' [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($SplittedName[0], $SplittedName[1]) } else { [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal) } } else { return } foreach ($SubACL in $ACL) { $OutputRequiresCommit = foreach ($Rule in $AccessRule) { $AccessRuleToAdd = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $Rule, $AccessControlType) try { Write-Verbose "Add-ADACL - Adding access for $($AccessRuleToAdd.IdentityReference) / $($AccessRuleToAdd.ActiveDirectoryRights)" $SubACL.ACL.AddAccessRule($AccessRuleToAdd) $true } catch { Write-Warning "Add-ADACL - Error adding permissions for $($AccessRuleToAdd.IdentityReference) / $($AccessRuleToAdd.ActiveDirectoryRights) due to error: $($_.Exception.Message)" $false } } if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) { Write-Verbose "Add-ADACL - Saving permissions for $($SubACL.DistinguishedName)" Set-Acl -Path $SubACL.Path -AclObject $SubACL.ACL -ErrorAction Stop } elseif ($OutputRequiresCommit -contains $false) { Write-Warning "Add-ADACL - Skipping saving permissions for $($SubACL.DistinguishedName) due to errors." } } } function Get-ADACL { [cmdletbinding()] param([Parameter(ValueFromPipeline)][Array] $ADObject, [string] $Domain = $Env:USERDNSDOMAIN, [Object] $Server, [string] $ForestName, [switch] $Extended, [switch] $ResolveTypes, [switch] $Inherited, [switch] $NotInherited, [switch] $Bundle, [System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRights, [System.DirectoryServices.ActiveDirectoryRights[]] $ExcludeActiveDirectoryRights, [System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $IncludeActiveDirectorySecurityInheritance, [System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $ExcludeActiveDirectorySecurityInheritance) Begin { if (-not $Script:ForestGUIDs) { Write-Verbose "Get-ADACL - Gathering Forest GUIDS" $Script:ForestGUIDs = Get-WinADForestGUIDs } if ($ResolveTypes) { if (-not $Script:ForestCache) { Write-Verbose "Get-ADACL - Building Cache" $Script:ForestCache = Get-WinADCache -ByNetBiosName } } } Process { foreach ($Object in $ADObject) { if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) { [string] $DistinguishedName = $Object.DistinguishedName [string] $CanonicalName = $Object.CanonicalName [string] $ObjectClass = $Object.ObjectClass } elseif ($Object -is [string]) { [string] $DistinguishedName = $Object [string] $CanonicalName = '' [string] $ObjectClass = '' } else { Write-Warning "Get-ADACL - Object not recognized. Skipping..." continue } $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDC) -replace '=' -replace ',' if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { Write-Verbose "Get-ADACL - Enabling PSDrives for $DistinguishedName to $DNConverted" New-ADForestDrives -ForestName $ForestName if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { Write-Warning "Get-ADACL - Drive $DNConverted not mapped. Terminating..." return } } Write-Verbose "Get-ADACL - Getting ACL from $DistinguishedName" try { $PathACL = "$DNConverted`:\$($DistinguishedName)" $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop } catch { Write-Warning "Get-ADACL - Path $PathACL - Error: $($_.Exception.Message)" } $AccessObjects = foreach ($ACL in $ACLs.Access) { [Array] $ADRights = $ACL.ActiveDirectoryRights -split ', ' if ($Inherited) { if ($ACL.IsInherited -eq $false) { continue } } if ($NotInherited) { if ($ACL.IsInherited -eq $true) { continue } } if ($IncludeActiveDirectoryRights) { $FoundInclude = $false foreach ($Right in $ADRights) { if ($IncludeActiveDirectoryRights -contains $Right) { $FoundInclude = $true break } } if (-not $FoundInclude) { continue } } if ($ExcludeActiveDirectoryRights) { foreach ($Right in $ADRights) { $FoundExclusion = $false if ($ExcludeActiveDirectoryRights -contains $Right) { $FoundExclusion = $true break } if ($FoundExclusion) { continue } } } if ($IncludeActiveDirectorySecurityInheritance) { if ($IncludeActiveDirectorySecurityInheritance -notcontains $ACL.InheritanceType) { continue } } if ($ExcludeActiveDirectorySecurityInheritance) { if ($ExcludeActiveDirectorySecurityInheritance -contains $ACL.InheritanceType) { continue } } if ($ACL.IdentityReference -like '*\*') { if ($ResolveTypes -and $Script:ForestCache) { $TemporaryIdentity = $Script:ForestCache["$($ACL.IdentityReference)"] $IdentityReferenceType = $TemporaryIdentity.ObjectClass $IdentityReference = $ACL.IdentityReference.Value } else { $IdentityReferenceType = '' $IdentityReference = $ACL.IdentityReference.Value } } elseif ($ACL.IdentityReference -like '*-*-*-*') { $ConvertedSID = ConvertFrom-SID -SID $ACL.IdentityReference if ($ResolveTypes -and $Script:ForestCache) { $TemporaryIdentity = $Script:ForestCache["$($ConvertedSID.Name)"] $IdentityReferenceType = $TemporaryIdentity.ObjectClass } else { $IdentityReferenceType = '' } $IdentityReference = $ConvertedSID.Name } else { $IdentityReference = $ACL.IdentityReference $IdentityReferenceType = 'Unknown' } $ReturnObject = [ordered] @{} $ReturnObject['DistinguishedName' ] = $DistinguishedName if ($CanonicalName) { $ReturnObject['CanonicalName'] = $CanonicalName } if ($ObjectClass) { $ReturnObject['ObjectClass'] = $ObjectClass } $ReturnObject['AccessControlType'] = $ACL.AccessControlType $ReturnObject['Principal'] = $IdentityReference if ($ResolveTypes) { $ReturnObject['PrincipalType'] = $IdentityReferenceType } $ReturnObject['ObjectTypeName'] = $Script:ForestGUIDs["$($ACL.objectType)"] $ReturnObject['InheritedObjectTypeName'] = $Script:ForestGUIDs["$($ACL.inheritedObjectType)"] $ReturnObject['ActiveDirectoryRights'] = $ACL.ActiveDirectoryRights $ReturnObject['InheritanceType'] = $ACL.InheritanceType $ReturnObject['IsInherited'] = $ACL.IsInherited if ($Extended) { $ReturnObject['ObjectType'] = $ACL.ObjectType $ReturnObject['InheritedObjectType'] = $ACL.InheritedObjectType $ReturnObject['ObjectFlags'] = $ACL.ObjectFlags $ReturnObject['InheritanceFlags'] = $ACL.InheritanceFlags $ReturnObject['PropagationFlags'] = $ACL.PropagationFlags } if ($Bundle) { $ReturnObject['Bundle'] = $ACL } [PSCustomObject] $ReturnObject } if ($Bundle) { [PSCustomObject] @{DistinguishedName = $DistinguishedName CanonicalName = $Object.CanonicalName ACL = $ACLs ACLAccessRules = $AccessObjects Path = $PathACL } } else { $AccessObjects } } } End {} } function Get-ADACLOwner { [cmdletBinding()] param([Array] $ADObject, [switch] $Resolve, [System.Collections.IDictionary] $ADAdministrativeGroups, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation) Begin { if (-not $ADAdministrativeGroups -and $Resolve) { $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation } } Process { foreach ($Object in $ADObject) { if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) { [string] $DistinguishedName = $Object.DistinguishedName [string] $CanonicalName = $Object.CanonicalName [string] $ObjectClass = $Object.ObjectClass } elseif ($Object -is [string]) { [string] $DistinguishedName = $Object [string] $CanonicalName = '' [string] $ObjectClass = '' } else { Write-Warning "Get-ADACLOwner - Object not recognized. Skipping..." continue } $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDC) -replace '=' -replace ',' if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { Write-Verbose "Get-ADACLOwner - Enabling PSDrives for $DistinguishedName to $DNConverted" New-ADForestDrives -ForestName $ForestName if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { Write-Warning "Set-ADACLOwner - Drive $DNConverted not mapped. Terminating..." return } } $PathACL = "$DNConverted`:\$($DistinguishedName)" try { $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop $Hash = [ordered] @{DistinguishedName = $DistinguishedName Owner = $ACLs.Owner ACLs = $ACLs } $ErrorMessage = '' } catch { $Hash = [ordered] @{DistinguishedName = $DistinguishedName Owner = $null ACLs = $null } $ErrorMessage = $_.Exception.Message } if ($Resolve) { if ($null -eq $Hash.Owner) { $Identity = $null } else { $Identity = Convert-Identity -Identity $Hash.Owner } if ($Identity) { $Hash['OwnerName'] = $Identity.Name $Hash['OwnerSid'] = $Identity.SID $Hash['OwnerType'] = $Identity.Type } else { $Hash['OwnerName'] = '' $Hash['OwnerSid'] = '' $Hash['OwnerType'] = '' } } $Hash['Error'] = $ErrorMessage [PSCustomObject] $Hash } } End {} } function Get-WinADBitlockerLapsSummary { [CmdletBinding(DefaultParameterSetName = 'Default')] param([Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'LapsOnly')] [Parameter(ParameterSetName = 'BitlockerOnly')] [alias('ForestName')][string] $Forest, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'LapsOnly')] [Parameter(ParameterSetName = 'BitlockerOnly')] [alias('Domain', 'Domains')][string[]] $IncludeDomains, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'LapsOnly')] [Parameter(ParameterSetName = 'BitlockerOnly')] [string[]] $ExcludeDomains, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'LapsOnly')] [Parameter(ParameterSetName = 'BitlockerOnly')] [string] $Filter = '*', [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'LapsOnly')] [Parameter(ParameterSetName = 'BitlockerOnly')] [string] $SearchBase, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'LapsOnly')] [Parameter(ParameterSetName = 'BitlockerOnly')] [ValidateSet('Base', 'OneLevel', 'SubTree', 'None')] [string] $SearchScope = 'None', [Parameter(ParameterSetName = 'LapsOnly')][switch] $LapsOnly, [Parameter(ParameterSetName = 'BitlockerOnly')][switch] $BitlockerOnly, [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'LapsOnly')] [Parameter(ParameterSetName = 'BitlockerOnly')] [System.Collections.IDictionary] $ExtendedForestInformation) $Today = Get-Date $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $ComputerProperties = Get-WinADForestSchemaProperties -Schema 'Computers' -Forest $Forest -ExtendedForestInformation $ForestInformation if ($ComputerProperties.Name -contains 'ms-Mcs-AdmPwd') { $LapsAvailable = $true $Properties = @('Name' 'OperatingSystem' 'OperatingSystemVersion' 'DistinguishedName' 'LastLogonDate' 'PasswordLastSet' 'ms-Mcs-AdmPwd' 'ms-Mcs-AdmPwdExpirationTime') } else { $LapsAvailable = $false $Properties = @('Name' 'OperatingSystem' 'OperatingSystemVersion' 'DistinguishedName' 'LastLogonDate' 'PasswordLastSet') } $CurrentDate = Get-Date $FormattedComputers = foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $Parameters = @{} if ($SearchScope -ne 'None') { $Parameters.SearchScope = $SearchScope } if ($SearchBase) { $DomainInformation = Get-ADDomain -Server $QueryServer $DNExtract = ConvertFrom-DistinguishedName -DistinguishedName $SearchBase -ToDC if ($DNExtract -eq $DomainInformation.DistinguishedName) { $Parameters.SearchBase = $SearchBase } else { continue } } try { $Computers = Get-ADComputer -Filter $Filter -Properties $Properties -Server $QueryServer @Parameters -ErrorAction Stop } catch { Write-Warning "Get-WinADBitlockerLapsSummary - Error getting computers $($_.Exception.Message)" } foreach ($_ in $Computers) { if ($LapsOnly -or -not $BitlockerOnly) { if ($LapsAvailable) { if ($_.'ms-Mcs-AdmPwdExpirationTime') { $Laps = $true $LapsExpirationDays = Convert-TimeToDays -StartTime ($CurrentDate) -EndTime (Convert-ToDateTime -Timestring ($_.'ms-Mcs-AdmPwdExpirationTime')) $LapsExpirationTime = Convert-ToDateTime -Timestring ($_.'ms-Mcs-AdmPwdExpirationTime') } else { $Laps = $false $LapsExpirationDays = $null $LapsExpirationTime = $null } } else { $Laps = 'N/A' } } if (-not $LapsOnly -or $BitlockerOnly) { [Array] $Bitlockers = Get-ADObject -Server $QueryServer -Filter 'objectClass -eq "msFVE-RecoveryInformation"' -SearchBase $_.DistinguishedName -Properties 'WhenCreated', 'msFVE-RecoveryPassword' | Sort-Object -Descending if ($Bitlockers) { $Encrypted = $true $EncryptedTime = $Bitlockers[0].WhenCreated } else { $Encrypted = $false $EncryptedTime = $null } } if ($null -ne $_.LastLogonDate) { [int] $LastLogonDays = "$(-$($_.LastLogonDate - $Today).Days)" } else { $LastLogonDays = $null } if ($null -ne $_.PasswordLastSet) { [int] $PasswordLastChangedDays = "$(-$($_.PasswordLastSet - $Today).Days)" } else { $PasswordLastChangedDays = $null } if ($LapsOnly) { [PSCustomObject] @{Name = $_.Name Enabled = $_.Enabled Domain = $Domain DNSHostName = $_.DNSHostName DistinguishedName = $_.DistinguishedName System = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion LastLogonDate = $_.LastLogonDate LastLogonDays = $LastLogonDays PasswordLastSet = $_.PasswordLastSet PasswordLastChangedDays = $PasswordLastChangedDays Laps = $Laps LapsExpirationDays = $LapsExpirationDays LapsExpirationTime = $LapsExpirationTime OrganizationalUnit = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit } } elseif ($BitlockerOnly) { [PSCustomObject] @{Name = $_.Name Enabled = $_.Enabled Domain = $Domain DNSHostName = $_.DNSHostName DistinguishedName = $_.DistinguishedName System = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion LastLogonDate = $_.LastLogonDate LastLogonDays = $LastLogonDays PasswordLastSet = $_.PasswordLastSet PasswordLastChangedDays = $PasswordLastChangedDays Encrypted = $Encrypted EncryptedTime = $EncryptedTime OrganizationalUnit = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit } } else { [PSCustomObject] @{Name = $_.Name Enabled = $_.Enabled Domain = $Domain DNSHostName = $_.DNSHostName DistinguishedName = $_.DistinguishedName System = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion LastLogonDate = $_.LastLogonDate LastLogonDays = $LastLogonDays PasswordLastSet = $_.PasswordLastSet PasswordLastChangedDays = $PasswordLastChangedDays Encrypted = $Encrypted EncryptedTime = $EncryptedTime Laps = $Laps LapsExpirationDays = $LapsExpirationDays LapsExpirationTime = $LapsExpirationTime OrganizationalUnit = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit } } } } $FormattedComputers } function Get-WinADDFSHealth { [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [int] $EventDays = 1, [System.Collections.IDictionary] $ExtendedForestInformation) $Today = (Get-Date) $Yesterday = (Get-Date -Hour 0 -Second 0 -Minute 0 -Millisecond 0).AddDays(-$EventDays) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation [Array] $Table = foreach ($Domain in $ForestInformation.Domains) { Write-Verbose "Get-WinADDFSHealth - Processing $Domain" $DomainControllersFull = $ForestInformation['DomainDomainControllers']["$Domain"] $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] try { [Array]$GPOs = @(Get-GPO -All -Domain $Domain -Server $QueryServer) } catch { $GPOs = $null } try { $CentralRepository = Get-ChildItem -Path "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" -ErrorAction Stop $CentralRepositoryDomain = if ($CentralRepository) { $true } else { $false } } catch { $CentralRepositoryDomain = $false } foreach ($DC in $DomainControllersFull) { Write-Verbose "Get-WinADDFSHealth - Processing $($DC.HostName) for $Domain" $DCName = $DC.Name $Hostname = $DC.Hostname $DN = $DC.DistinguishedName $LocalSettings = "CN=DFSR-LocalSettings,$DN" $Subscriber = "CN=Domain System Volume,$LocalSettings" $Subscription = "CN=SYSVOL Subscription,$Subscriber" $ReplicationStatus = @{'0' = 'Uninitialized' '1' = 'Initialized' '2' = 'Initial synchronization' '3' = 'Auto recovery' '4' = 'Normal' '5' = 'In error state' '6' = 'Disabled' '7' = 'Unknown' } $DomainSummary = [ordered] @{"DomainController" = $DCName "Domain" = $Domain "Status" = $false "ReplicationState" = 'Unknown' "IsPDC" = $DC.IsPDC 'GroupPolicyOutput' = $null -ne $GPOs "GroupPolicyCount" = if ($GPOs) { $GPOs.Count } else { 0 } "SYSVOLCount" = 0 'CentralRepository' = $CentralRepositoryDomain 'CentralRepositoryDC' = $false 'IdenticalCount' = $false "Availability" = $false "MemberReference" = $false "DFSErrors" = 0 "DFSEvents" = $null "DFSLocalSetting" = $false "DomainSystemVolume" = $false "SYSVOLSubscription" = $false "StopReplicationOnAutoRecovery" = $false "DFSReplicatedFolderInfo" = $null } $WarningVar = $null $DFSReplicatedFolderInfoAll = Get-CimData -NameSpace "root\microsoftdfs" -Class 'dfsrreplicatedfolderinfo' -ComputerName $Hostname -WarningAction SilentlyContinue -WarningVariable WarningVar $DFSReplicatedFolderInfo = $DFSReplicatedFolderInfoAll | Where-Object { $_.ReplicationGroupName -eq 'Domain System Volume' } if ($WarningVar) { $DomainSummary['ReplicationState'] = 'Unknown' } else { $DomainSummary['ReplicationState'] = $ReplicationStatus["$($DFSReplicatedFolderInfo.State)"] } try { $CentralRepositoryDC = Get-ChildItem -Path "\\$Hostname\SYSVOL\$Domain\policies\PolicyDefinitions" -ErrorAction Stop $DomainSummary['CentralRepositoryDC'] = if ($CentralRepositoryDC) { $true } else { $false } } catch { $DomainSummary['CentralRepositoryDC'] = $false } try { $MemberReference = (Get-ADObject $Subscriber -Properties msDFSR-MemberReference -Server $QueryServer -ErrorAction Stop).'msDFSR-MemberReference' -like "CN=$DCName,*" $DomainSummary['MemberReference'] = if ($MemberReference) { $true } else { $false } } catch { $DomainSummary['MemberReference'] = $false } try { $DFSLocalSetting = Get-ADObject $LocalSettings -Server $QueryServer -ErrorAction Stop $DomainSummary['DFSLocalSetting'] = if ($DFSLocalSetting) { $true } else { $false } } catch { $DomainSummary['DFSLocalSetting'] = $false } try { $DomainSystemVolume = Get-ADObject $Subscriber -Server $QueryServer -ErrorAction Stop $DomainSummary['DomainSystemVolume'] = if ($DomainSystemVolume) { $true } else { $false } } catch { $DomainSummary['DomainSystemVolume'] = $false } try { $SysVolSubscription = Get-ADObject $Subscription -Server $QueryServer -ErrorAction Stop $DomainSummary['SYSVOLSubscription'] = if ($SysVolSubscription) { $true } else { $false } } catch { $DomainSummary['SYSVOLSubscription'] = $false } try { [Array] $SYSVOL = Get-ChildItem -Path "\\$Hostname\SYSVOL\$Domain\Policies" -Exclude "PolicyDefinitions*" -ErrorAction Stop $DomainSummary['SysvolCount'] = $SYSVOL.Count } catch { $DomainSummary['SysvolCount'] = 0 } if (Test-Connection $Hostname -ErrorAction SilentlyContinue) { $DomainSummary['Availability'] = $true } else { $DomainSummary['Availability'] = $false } try { [Array] $Events = Get-Events -LogName "DFS Replication" -Level Error -ComputerName $Hostname -DateFrom $Yesterday -DateTo $Today $DomainSummary['DFSErrors'] = $Events.Count $DomainSummary['DFSEvents'] = $Events } catch { $DomainSummary['DFSErrors'] = $null } $DomainSummary['IdenticalCount'] = $DomainSummary['GroupPolicyCount'] -eq $DomainSummary['SYSVOLCount'] try { $Registry = Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\DFSR\Parameters" -ComputerName $Hostname -ErrorAction Stop } catch { $Registry = $null } if ($null -ne $Registry.StopReplicationOnAutoRecovery) { $DomainSummary['StopReplicationOnAutoRecovery'] = [bool] $Registry.StopReplicationOnAutoRecovery } else { $DomainSummary['StopReplicationOnAutoRecovery'] = $null } $DomainSummary['DFSReplicatedFolderInfo'] = $DFSReplicatedFolderInfoAll $All = @($DomainSummary['GroupPolicyOutput'] $DomainSummary['SYSVOLSubscription'] $DomainSummary['ReplicationState'] -eq 'Normal' $DomainSummary['DomainSystemVolume'] $DomainSummary['DFSLocalSetting'] $DomainSummary['MemberReference'] $DomainSummary['Availability'] $DomainSummary['IdenticalCount'] $DomainSummary['DFSErrors'] -eq 0) $DomainSummary['Status'] = $All -notcontains $false [PSCustomObject] $DomainSummary } } $Table } function Get-WinADDiagnostics { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [System.Collections.IDictionary] $ExtendedForestInformation) $LevelsDictionary = @{'0' = 'None' '1' = 'Minimal' '2' = 'Basic' '3' = 'Extensive' '4' = 'Verbose' '5' = 'Internal' '' = 'Unknown' } $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation [Array] $Computers = $ForestInformation.ForestDomainControllers.HostName foreach ($Computer in $Computers) { try { $Output = Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' -ComputerName $Computer -Verbose:$false -ErrorAction Stop } catch { $ErrorMessage1 = $_.Exception.Message $Output = $null } try { $Output1 = Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -ComputerName $Computer -Verbose:$false -ErrorAction Stop if ($Output1.DbFlag -eq 545325055) { $Netlogon = $true } else { $Netlogon = $false } } catch { $ErrorMessage2 = $_.Exception.Message $Netlogon = 'Unknown' } if (-not $ErrorMessage1 -and -not $ErrorMessage2) { $Comment = 'OK' [PSCustomObject] @{'ComputerName' = $Computer 'Knowledge Consistency Checker (KCC)' = $LevelsDictionary["$($Output.'1 Knowledge Consistency Checker')"] 'Security Events' = $LevelsDictionary["$($Output.'2 Security Events')"] 'ExDS Interface Events' = $LevelsDictionary["$($Output.'3 ExDS Interface Events')"] 'MAPI Interface Events' = $LevelsDictionary["$($Output.'4 MAPI Interface Events')"] 'Replication Events' = $LevelsDictionary["$($Output.'5 Replication Events')"] 'Garbage Collection' = $LevelsDictionary["$($Output.'6 Garbage Collection')"] 'Internal Configuration' = $LevelsDictionary["$($Output.'7 Internal Configuration')"] 'Directory Access' = $LevelsDictionary["$($Output.'8 Directory Access')"] 'Internal Processing' = $LevelsDictionary["$($Output.'9 Internal Processing')"] 'Performance Counters' = $LevelsDictionary["$($Output.'10 Performance Counters')"] 'Initialization / Termination' = $LevelsDictionary["$($Output.'11 Initialization/Termination')"] 'Service Control' = $LevelsDictionary["$($Output.'12 Service Control')"] 'Name Resolution' = $LevelsDictionary["$($Output.'13 Name Resolution')"] 'Backup' = $LevelsDictionary["$($Output.'14 Backup')"] 'Field Engineering' = $LevelsDictionary["$($Output.'15 Field Engineering')"] 'LDAP Interface Events' = $LevelsDictionary["$($Output.'16 LDAP Interface Events')"] 'Setup' = $LevelsDictionary["$($Output.'17 Setup')"] 'Global Catalog' = $LevelsDictionary["$($Output.'18 Global Catalog')"] 'Inter-site Messaging' = $LevelsDictionary["$($Output.'19 Inter-site Messaging')"] 'Group Caching' = $LevelsDictionary["$($Output.'20 Group Caching')"] 'Linked-Value Replication' = $LevelsDictionary["$($Output.'21 Linked-Value Replication')"] 'DS RPC Client' = $LevelsDictionary["$($Output.'22 DS RPC Client')"] 'DS RPC Server' = $LevelsDictionary["$($Output.'23 DS RPC Server')"] 'DS Schema' = $LevelsDictionary["$($Output.'24 DS Schema')"] 'Transformation Engine' = $LevelsDictionary["$($Output.'25 Transformation Engine')"] 'Claims-Based Access Control' = $LevelsDictionary["$($Output.'26 Claims-Based Access Control')"] 'Netlogon' = $Netlogon 'Comment' = $Comment } } else { $Comment = $ErrorMessage1 + ' ' + $ErrorMessage2 [PSCustomObject] @{'ComputerName' = $Computer 'Knowledge Consistency Checker (KCC)' = $LevelsDictionary["$($Output.'1 Knowledge Consistency Checker')"] 'Security Events' = $LevelsDictionary["$($Output.'2 Security Events')"] 'ExDS Interface Events' = $LevelsDictionary["$($Output.'3 ExDS Interface Events')"] 'MAPI Interface Events' = $LevelsDictionary["$($Output.'4 MAPI Interface Events')"] 'Replication Events' = $LevelsDictionary["$($Output.'5 Replication Events')"] 'Garbage Collection' = $LevelsDictionary["$($Output.'6 Garbage Collection')"] 'Internal Configuration' = $LevelsDictionary["$($Output.'7 Internal Configuration')"] 'Directory Access' = $LevelsDictionary["$($Output.'8 Directory Access')"] 'Internal Processing' = $LevelsDictionary["$($Output.'9 Internal Processing')"] 'Performance Counters' = $LevelsDictionary["$($Output.'10 Performance Counters')"] 'Initialization / Termination' = $LevelsDictionary["$($Output.'11 Initialization/Termination')"] 'Service Control' = $LevelsDictionary["$($Output.'12 Service Control')"] 'Name Resolution' = $LevelsDictionary["$($Output.'13 Name Resolution')"] 'Backup' = $LevelsDictionary["$($Output.'14 Backup')"] 'Field Engineering' = $LevelsDictionary["$($Output.'15 Field Engineering')"] 'LDAP Interface Events' = $LevelsDictionary["$($Output.'16 LDAP Interface Events')"] 'Setup' = $LevelsDictionary["$($Output.'17 Setup')"] 'Global Catalog' = $LevelsDictionary["$($Output.'18 Global Catalog')"] 'Inter-site Messaging' = $LevelsDictionary["$($Output.'19 Inter-site Messaging')"] 'Group Caching' = $LevelsDictionary["$($Output.'20 Group Caching')"] 'Linked-Value Replication' = $LevelsDictionary["$($Output.'21 Linked-Value Replication')"] 'DS RPC Client' = $LevelsDictionary["$($Output.'22 DS RPC Client')"] 'DS RPC Server' = $LevelsDictionary["$($Output.'23 DS RPC Server')"] 'DS Schema' = $LevelsDictionary["$($Output.'24 DS Schema')"] 'Transformation Engine' = $LevelsDictionary["$($Output.'25 Transformation Engine')"] 'Claims-Based Access Control' = $LevelsDictionary["$($Output.'26 Claims-Based Access Control')"] 'Netlogon' = $Netlogon 'Comment' = $Comment } } } } function Get-WinADDuplicateObject { [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] Get-ADObject -LDAPFilter "(|(cn=*\0ACNF:*)(ou=*OACNF:*))" -SearchScope Subtree -Server $QueryServer } } Function Get-WinADForestObjectsConflict { [CmdletBinding()] Param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $DC = $ForestInformation['QueryServers']["$Domain"].HostName[0] Get-ADObject -LDAPFilter "(|(cn=*\0ACNF:*)(ou=*CNF:*))" -Properties WhenChanged -Server $DC | ForEach-Object { $LiveObject = $null $ConflictObject = [PSCustomObject] @{Name = $_ ConflictDn = $_.DistinguishedName ConflictWhenChanged = $_.WhenChanged LiveDn = "N/A" LiveWhenChanged = "N/A" } if (Select-String -SimpleMatch "\0ACNF:" -InputObject $ConflictObject.ConflictDn) { $SplitConfDN = $ConflictObject.ConflictDn -split "0ACNF:" try { $LiveObject = Get-ADObject -Identity "$($SplitConfDN[0].TrimEnd("\"))$($SplitConfDN[1].Substring(36))" -Properties WhenChanged -Server $DC -ErrorAction Stop } catch {} if ($LiveObject) { $ConflictObject.LiveDN = $LiveObject.DistinguishedName $ConflictObject.LiveWhenChanged = $LiveObject.WhenChanged } } else { $SplitConfDN = $ConflictObject.ConflictDn -split "CNF:" try { $LiveObject = Get-ADObject -Identity "$($SplitConfDN[0])$($SplitConfDN[1].Substring(36))" -Properties WhenChanged -Server $DC -ErrorAction Stop } catch {} if ($LiveObject) { $ConflictObject.LiveDN = $LiveObject.DistinguishedName $ConflictObject.LiveWhenChanged = $LiveObject.WhenChanged } } $ConflictObject } } } function Get-WinADForestOptionalFeatures { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [Array] $ComputerProperties, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation if (-not $ComputerProperties) { $ComputerProperties = Get-WinADForestSchemaProperties -Schema 'Computers' -Forest $Forest -ExtendedForestInformation $ForestInformation } $QueryServer = $ForestInformation['QueryServers']["Forest"].HostName[0] $LapsProperties = 'ms-Mcs-AdmPwd' $OptionalFeatures = $(Get-ADOptionalFeature -Filter * -Server $QueryServer) $Optional = [ordered]@{'Recycle Bin Enabled' = $false 'Privileged Access Management Feature Enabled' = $false 'Laps Enabled' = ($ComputerProperties.Name -contains $LapsProperties) } foreach ($Feature in $OptionalFeatures) { if ($Feature.Name -eq 'Recycle Bin Feature') { $Optional.'Recycle Bin Enabled' = $Feature.EnabledScopes.Count -gt 0 } if ($Feature.Name -eq 'Privileged Access Management Feature') { $Optional.'Privileged Access Management Feature Enabled' = $Feature.EnabledScopes.Count -gt 0 } } $Optional } function Get-WinADForestReplication { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [switch] $Extended, [System.Collections.IDictionary] $ExtendedForestInformation) $ProcessErrors = [System.Collections.Generic.List[PSCustomObject]]::new() $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation $Replication = foreach ($DC in $ForestInformation.ForestDomainControllers) { try { Get-ADReplicationPartnerMetadata -Target $DC.HostName -Partition * -ErrorAction Stop } catch { Write-Warning -Message "Get-WinADForestReplication - Error on server $($_.Exception.ServerName): $($_.Exception.Message)" $ProcessErrors.Add([PSCustomObject] @{Server = $_.Exception.ServerName; StatusMessage = $_.Exception.Message }) } } foreach ($_ in $Replication) { $ServerPartner = (Resolve-DnsName -Name $_.PartnerAddress -Verbose:$false -ErrorAction SilentlyContinue) $ServerInitiating = (Resolve-DnsName -Name $_.Server -Verbose:$false -ErrorAction SilentlyContinue) $ReplicationObject = [ordered] @{Server = $_.Server ServerIPV4 = $ServerInitiating.IP4Address ServerPartner = $ServerPartner.NameHost ServerPartnerIPV4 = $ServerPartner.IP4Address LastReplicationAttempt = $_.LastReplicationAttempt LastReplicationResult = $_.LastReplicationResult LastReplicationSuccess = $_.LastReplicationSuccess ConsecutiveReplicationFailures = $_.ConsecutiveReplicationFailures LastChangeUsn = $_.LastChangeUsn PartnerType = $_.PartnerType Partition = $_.Partition TwoWaySync = $_.TwoWaySync ScheduledSync = $_.ScheduledSync SyncOnStartup = $_.SyncOnStartup CompressChanges = $_.CompressChanges DisableScheduledSync = $_.DisableScheduledSync IgnoreChangeNotifications = $_.IgnoreChangeNotifications IntersiteTransport = $_.IntersiteTransport IntersiteTransportGuid = $_.IntersiteTransportGuid IntersiteTransportType = $_.IntersiteTransportType UsnFilter = $_.UsnFilter Writable = $_.Writable Status = if ($_.LastReplicationResult -ne 0) { $false } else { $true } StatusMessage = "Last successful replication time was $($_.LastReplicationSuccess), Consecutive Failures: $($_.ConsecutiveReplicationFailures)" } if ($Extended) { $ReplicationObject.Partner = $_.Partner $ReplicationObject.PartnerAddress = $_.PartnerAddress $ReplicationObject.PartnerGuid = $_.PartnerGuid $ReplicationObject.PartnerInvocationId = $_.PartnerInvocationId $ReplicationObject.PartitionGuid = $_.PartitionGuid } [PSCustomObject] $ReplicationObject } foreach ($_ in $ProcessErrors) { if ($null -ne $_.Server) { $ServerInitiating = (Resolve-DnsName -Name $_.Server -Verbose:$false -ErrorAction SilentlyContinue) } else { $ServerInitiating = [PSCustomObject] @{IP4Address = '127.0.0.1' } } $ReplicationObject = [ordered] @{Server = $_.Server ServerIPV4 = $ServerInitiating.IP4Address ServerPartner = 'Unknown' ServerPartnerIPV4 = '127.0.0.1' LastReplicationAttempt = $null LastReplicationResult = $null LastReplicationSuccess = $null ConsecutiveReplicationFailures = $null LastChangeUsn = $null PartnerType = $null Partition = $null TwoWaySync = $null ScheduledSync = $null SyncOnStartup = $null CompressChanges = $null DisableScheduledSync = $null IgnoreChangeNotifications = $null IntersiteTransport = $null IntersiteTransportGuid = $null IntersiteTransportType = $null UsnFilter = $null Writable = $null Status = $false StatusMessage = $_.StatusMessage } if ($Extended) { $ReplicationObject.Partner = $null $ReplicationObject.PartnerAddress = $null $ReplicationObject.PartnerGuid = $null $ReplicationObject.PartnerInvocationId = $null $ReplicationObject.PartitionGuid = $null } [PSCustomObject] $ReplicationObject } } function Get-WinADForestRoles { [alias('Get-WinADRoles', 'Get-WinADDomainRoles')] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [switch] $Formatted, [string] $Splitter = ', ', [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation $Roles = [ordered] @{SchemaMaster = $null DomainNamingMaster = $null PDCEmulator = $null RIDMaster = $null InfrastructureMaster = $null IsReadOnly = $null IsGlobalCatalog = $null } foreach ($_ in $ForestInformation.ForestDomainControllers) { if ($_.IsSchemaMaster -eq $true) { $Roles['SchemaMaster'] = if ($null -ne $Roles['SchemaMaster']) { @($Roles['SchemaMaster']) + $_.HostName } else { $_.HostName } } if ($_.IsDomainNamingMaster -eq $true) { $Roles['DomainNamingMaster'] = if ($null -ne $Roles['DomainNamingMaster']) { @($Roles['DomainNamingMaster']) + $_.HostName } else { $_.HostName } } if ($_.IsPDC -eq $true) { $Roles['PDCEmulator'] = if ($null -ne $Roles['PDCEmulator']) { @($Roles['PDCEmulator']) + $_.HostName } else { $_.HostName } } if ($_.IsRIDMaster -eq $true) { $Roles['RIDMaster'] = if ($null -ne $Roles['RIDMaster']) { @($Roles['RIDMaster']) + $_.HostName } else { $_.HostName } } if ($_.IsInfrastructureMaster -eq $true) { $Roles['InfrastructureMaster'] = if ($null -ne $Roles['InfrastructureMaster']) { @($Roles['InfrastructureMaster']) + $_.HostName } else { $_.HostName } } if ($_.IsReadOnly -eq $true) { $Roles['IsReadOnly'] = if ($null -ne $Roles['IsReadOnly']) { @($Roles['IsReadOnly']) + $_.HostName } else { $_.HostName } } if ($_.IsGlobalCatalog -eq $true) { $Roles['IsGlobalCatalog'] = if ($null -ne $Roles['IsGlobalCatalog']) { @($Roles['IsGlobalCatalog']) + $_.HostName } else { $_.HostName } } } if ($Formatted) { foreach ($_ in ([string[]] $Roles.Keys)) { $Roles[$_] = $Roles[$_] -join $Splitter } } $Roles } function Get-WinADForestSchemaProperties { [cmdletBinding()] param([alias('ForestName')][string] $Forest, [validateSet('Computers', 'Users')][string[]] $Schema = @('Computers', 'Users'), [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation if ($Forest) { $Type = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest $Context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new($Type, $ForestInformation.Forest) $CurrentSchema = [directoryservices.activedirectory.activedirectoryschema]::GetSchema($Context) } else { $CurrentSchema = [directoryservices.activedirectory.activedirectoryschema]::GetCurrentSchema() } if ($Schema -contains 'Computers') { $CurrentSchema.FindClass("computer").mandatoryproperties | Select-Object -Property name, commonname, description, syntax , SchemaGuid $CurrentSchema.FindClass("computer").optionalproperties | Select-Object -Property name, commonname, description, syntax, SchemaGuid } if ($Schema -contains 'Users') { $CurrentSchema.FindClass("user").mandatoryproperties | Select-Object -Property name, commonname, description, syntax, SchemaGuid $CurrentSchema.FindClass("user").optionalproperties | Select-Object -Property name, commonname, description, syntax, SchemaGuid } } function Get-WinADForestSites { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [switch] $Formatted, [string] $Splitter, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0] $Sites = Get-ADReplicationSite -Filter * -Properties * -Server $QueryServer foreach ($Site in $Sites) { [Array] $DCs = $ForestInformation.ForestDomainControllers | Where-Object { $_.Site -eq $Site.Name } [Array] $Subnets = ConvertFrom-DistinguishedName -DistinguishedName $Site.'Subnets' if ($Formatted) { [PSCustomObject] @{'Name' = $Site.Name 'Display Name' = $Site.'DisplayName' 'Description' = $Site.'Description' 'CanonicalName' = $Site.'CanonicalName' 'DistinguishedName' = $Site.'DistinguishedName' 'Location' = $Site.'Location' 'ManagedBy' = $Site.'ManagedBy' 'Protected From Accidental Deletion' = $Site.'ProtectedFromAccidentalDeletion' 'Redundant Server Topology Enabled' = $Site.'RedundantServerTopologyEnabled' 'Automatic Inter-Site Topology Generation Enabled' = $Site.'AutomaticInterSiteTopologyGenerationEnabled' 'Automatic Topology Generation Enabled' = $Site.'AutomaticTopologyGenerationEnabled' 'Subnets' = if ($Splitter) { $Subnets -join $Splitter } else { $Subnets } 'Subnets Count' = $Subnets.Count 'Domain Controllers' = if ($Splitter) { ($DCs).HostName -join $Splitter } else { ($DCs).HostName } 'Domain Controllers Count' = $DCs.Count 'sDRightsEffective' = $_.'sDRightsEffective' 'Topology Cleanup Enabled' = $_.'TopologyCleanupEnabled' 'Topology Detect Stale Enabled' = $_.'TopologyDetectStaleEnabled' 'Topology Minimum Hops Enabled' = $_.'TopologyMinimumHopsEnabled' 'Universal Group Caching Enabled' = $_.'UniversalGroupCachingEnabled' 'Universal Group Caching Refresh Site' = $_.'UniversalGroupCachingRefreshSite' 'Windows Server 2000 Bridgehead Selection Method Enabled' = $_.'WindowsServer2000BridgeheadSelectionMethodEnabled' 'Windows Server 2000 KCC ISTG Selection Behavior Enabled' = $_.'WindowsServer2000KCCISTGSelectionBehaviorEnabled' 'Windows Server 2003 KCC Behavior Enabled' = $_.'WindowsServer2003KCCBehaviorEnabled' 'Windows Server 2003 KCC Ignore Schedule Enabled' = $_.'WindowsServer2003KCCIgnoreScheduleEnabled' 'Windows Server 2003 KCC SiteLink Bridging Enabled' = $_.'WindowsServer2003KCCSiteLinkBridgingEnabled' 'Created' = $Site.Created 'Modified' = $Site.Modified 'Deleted' = $Site.Deleted } } else { [PSCustomObject] @{'Name' = $Site.Name 'DisplayName' = $Site.'DisplayName' 'Description' = $Site.'Description' 'CanonicalName' = $Site.'CanonicalName' 'DistinguishedName' = $Site.'DistinguishedName' 'Location' = $Site.'Location' 'ManagedBy' = $Site.'ManagedBy' 'ProtectedFromAccidentalDeletion' = $Site.'ProtectedFromAccidentalDeletion' 'RedundantServerTopologyEnabled' = $Site.'RedundantServerTopologyEnabled' 'AutomaticInterSiteTopologyGenerationEnabled' = $Site.'AutomaticInterSiteTopologyGenerationEnabled' 'AutomaticTopologyGenerationEnabled' = $Site.'AutomaticTopologyGenerationEnabled' 'Subnets' = if ($Splitter) { $Subnets -join $Splitter } else { $Subnets } 'SubnetsCount' = $Subnets.Count 'DomainControllers' = if ($Splitter) { ($DCs).HostName -join $Splitter } else { ($DCs).HostName } 'DomainControllersCount' = $DCs.Count 'sDRightsEffective' = $_.'sDRightsEffective' 'TopologyCleanupEnabled' = $_.'TopologyCleanupEnabled' 'TopologyDetectStaleEnabled' = $_.'TopologyDetectStaleEnabled' 'TopologyMinimumHopsEnabled' = $_.'TopologyMinimumHopsEnabled' 'UniversalGroupCachingEnabled' = $_.'UniversalGroupCachingEnabled' 'UniversalGroupCachingRefreshSite' = $_.'UniversalGroupCachingRefreshSite' 'WindowsServer2000BridgeheadSelectionMethodEnabled' = $_.'WindowsServer2000BridgeheadSelectionMethodEnabled' 'WindowsServer2000KCCISTGSelectionBehaviorEnabled' = $_.'WindowsServer2000KCCISTGSelectionBehaviorEnabled' 'WindowsServer2003KCCBehaviorEnabled' = $_.'WindowsServer2003KCCBehaviorEnabled' 'WindowsServer2003KCCIgnoreScheduleEnabled' = $_.'WindowsServer2003KCCIgnoreScheduleEnabled' 'WindowsServer2003KCCSiteLinkBridgingEnabled' = $_.'WindowsServer2003KCCSiteLinkBridgingEnabled' 'Created' = $Site.Created 'Modified' = $Site.Modified 'Deleted' = $Site.Deleted } } } } Function Get-WinADGPOMissingPermissions { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Domain Parameter description .EXAMPLE An example .NOTES Based on https://secureinfra.blog/2018/12/31/most-common-mistakes-in-active-directory-and-domain-services-part-1/ #> [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $SkipRODC, [System.Collections.IDictionary] $ExtendedForestInformation, [validateset('AuthenticatedUsers', 'DomainComputers', 'Either')][string] $Mode = 'Either', [switch] $Extended) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] if ($Extended) { $GPOs = Get-GPO -All -Domain $Domain -Server $QueryServer | ForEach-Object { [xml] $XMLContent = Get-GPOReport -ID $_.ID.Guid -ReportType XML -Server $ForestInformation.QueryServers[$Domain].HostName[0] -Domain $Domain Add-Member -InputObject $_ -MemberType NoteProperty -Name 'LinksTo' -Value $XMLContent.GPO.LinksTo if ($XMLContent.GPO.LinksTo) { Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Linked' -Value $true } else { Add-Member -InputObject $_ -MemberType NoteProperty -Name 'Linked' -Value $false } $_ | Select-Object -Property Id, DisplayName, DomainName, Owner, Linked, GpoStatus, CreationTime, ModificationTime, @{label = 'UserVersion' expression = { "AD Version: $($_.User.DSVersion), SysVol Version: $($_.User.SysvolVersion)" } }, @{label = 'ComputerVersion' expression = { "AD Version: $($_.Computer.DSVersion), SysVol Version: $($_.Computer.SysvolVersion)" } }, WmiFilter, Description, User, Computer, LinksTo } } else { $GPOs = Get-GPO -All -Domain $Domain -Server $QueryServer } $DomainInformation = Get-ADDomain -Server $QueryServer $DomainComputersSID = $('{0}-515' -f $DomainInformation.DomainSID.Value) $MissingPermissions = @(foreach ($GPO in $GPOs) { $Permissions = Get-GPPermission -Guid $GPO.Id -All -Server $QueryServer -DomainName $Domain | Select-Object -ExpandProperty Trustee if ($Mode -eq 'Either' -or $Mode -eq 'AuthenticatedUsers') { $GPOPermissionForAuthUsers = $Permissions | Where-Object { $_.Sid.Value -eq 'S-1-5-11' } } if ($Mode -eq 'Either' -or $Mode -eq 'DomainComputers') { $GPOPermissionForDomainComputers = $Permissions | Where-Object { $_.Sid.Value -eq $DomainComputersSID } } if ($Mode -eq 'Either') { If (-not $GPOPermissionForAuthUsers -and -not $GPOPermissionForDomainComputers) { $GPO } } elseif ($Mode -eq 'AuthenticatedUsers') { If (-not $GPOPermissionForAuthUsers) { $GPO } } elseif ($Mode -eq 'DomainComputers') { If (-not $GPOPermissionForDomainComputers) { $GPO } } }) $MissingPermissions } } function Get-WinADGPOSysvolFolders { [alias('Get-WinADGPOSysvol')] [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [Array] $GPOs, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { Write-Verbose "Get-WinADGPOSysvolFolders - Processing $Domain" $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] [Array]$GPOs = @(Get-GPO -All -Domain $Domain -Server $QueryServer) foreach ($Server in $ForestInformation['DomainDomainControllers']["$Domain"]) { Write-Verbose "Get-WinADGPOSysvolFolders - Processing $Domain \ $($Server.Hostname)" $Differences = @{} $SysvolHash = @{} $GPOGUIDS = $GPOs.ID.GUID try { $SYSVOL = Get-ChildItem -Path "\\$($Server.Hostname)\SYSVOL\$Domain\Policies" -ErrorAction Stop } catch { $Sysvol = $Null } foreach ($_ in $SYSVOL) { $GUID = $_.Name -replace '{' -replace '}' $SysvolHash[$GUID] = $_ } $Files = $SYSVOL.Name -replace '{' -replace '}' if ($Files) { $Comparing = Compare-Object -ReferenceObject $GPOGUIDS -DifferenceObject $Files -IncludeEqual foreach ($_ in $Comparing) { if ($_.SideIndicator -eq '==') { $Found = 'Exists' } elseif ($_.SideIndicator -eq '<=') { $Found = 'Not available on SYSVOL' } elseif ($_.SideIndicator -eq '=>') { $Found = 'Orphaned GPO' } else { $Found = 'Orphaned GPO' } $Differences[$_.InputObject] = $Found } } $GPOSummary = @(foreach ($GPO in $GPOS) { if ($null -ne $SysvolHash[$GPO.Id.GUID].FullName) { try { $ACL = Get-Acl -Path $SysvolHash[$GPO.Id.GUID].FullName -ErrorAction Stop } catch { Write-Warning "Get-WinADGPOSysvolFolders - ACL reading failed for $($SysvolHash[$GPO.Id.GUID].FullName) with error: $($_.Exception.Message)" $ACL = $null } } else { $ACL = $null } if ($null -eq $Differences[$GPO.Id.Guid]) { $SysVolStatus = 'Not available on SYSVOL' } else { $SysVolStatus = $Differences[$GPO.Id.Guid] } [PSCustomObject] @{DisplayName = $GPO.DisplayName Status = $Differences[$GPO.Id.Guid] DomainName = $GPO.DomainName SysvolServer = $Server.HostName SysvolStatus = $SysVolStatus Owner = $GPO.Owner FileOwner = $ACL.Owner Id = $GPO.Id.Guid GpoStatus = $GPO.GpoStatus Description = $GPO.Description CreationTime = $GPO.CreationTime ModificationTime = $GPO.ModificationTime UserVersion = $GPO.UserVersion ComputerVersion = $GPO.ComputerVersion WmiFilter = $GPO.WmiFilter } } foreach ($_ in $Differences.Keys) { if ($Differences[$_] -eq 'Orphaned GPO') { if ($SysvolHash[$_].BaseName -notcontains 'PolicyDefinitions') { if ($null -ne $SysvolHash[$_].FullName) { $ACL = Get-Acl -Path $SysvolHash[$_].FullName -ErrorAction SilentlyContinue } else { $ACL = $null } [PSCustomObject] @{DisplayName = $SysvolHash[$_].BaseName Status = 'Orphaned GPO' DomainName = $Domain SysvolServer = $Server.HostName SysvolStatus = $Differences[$GPO.Id.Guid] Owner = $ACL.Owner FileOwner = $ACL.Owner Id = $_ GpoStatus = 'Orphaned' Description = $null CreationTime = $SysvolHash[$_].CreationTime ModificationTime = $SysvolHash[$_].LastWriteTime UserVersion = $null ComputerVersion = $null WmiFilter = $null } } } }) $GPOSummary | Sort-Object -Property DisplayName } } } function Get-WinADGroupMember { [cmdletBinding()] param([alias('GroupName', 'Identity')][Parameter(ValuefromPipeline, Mandatory)][Array] $Group, [switch] $CountMembers, [switch] $AddSelf, [switch] $All, [switch] $ClearCache, [Parameter(DontShow)][int] $Nesting = -1, [Parameter(DontShow)][System.Collections.Generic.List[object]] $CollectedGroups, [Parameter(DontShow)][System.Object] $Circular, [Parameter(DontShow)][System.Collections.IDictionary] $InitialGroup, [Parameter(DontShow)][switch] $Nested) Begin { if (-not $Script:WinADGroupMemberCache -or $ClearCache -or ($Cache -and -not $Script:WinADGroupMemberCacheGlobal)) { if ($ClearCache) { $Script:WinADGroupMemberCacheGlobal = $false } $Script:WinADGroupMemberCache = @{} } if ($Nesting -eq -1) { $MembersCache = [ordered] @{} } } Process { $Output = foreach ($GroupName in $Group) { if (-not $Nested.IsPresent) { $InitialGroup = [ordered] @{GroupName = $GroupName Name = $null SamAccountName = $null DomainName = $null DisplayName = $null Enabled = $null Type = 'group' DirectMembers = 0 DirectGroups = 0 IndirectMembers = 0 TotalMembers = 0 Nesting = $Nesting Circular = $false TrustedDomain = $false ParentGroup = '' ParentGroupDomain = '' GroupDomainName = $null DistinguishedName = $null Sid = $null } $CollectedGroups = [System.Collections.Generic.List[string]]::new() $Nesting = -1 } $Nesting++ $ADGroupName = Get-WinADObject -Identity $GroupName if ($ADGroupName) { if (-not $Nested.IsPresent) { $InitialGroup.GroupName = $ADGroupName.Name $InitialGroup.DomainName = $ADGroupName.DomainName if ($AddSelf) { $InitialGroup.Name = $ADGroupName.Name $InitialGroup.SamAccountName = $ADGroupName.SamAccountName $InitialGroup.DisplayName = $ADGroupName.DisplayName $InitialGroup.GroupDomainName = $ADGroupName.DomainName $InitialGroup.DistinguishedName = $ADGroupName.DistinguishedName $InitialGroup.Sid = $ADGroupName.ObjectSID } } $Script:WinADGroupMemberCache[$ADGroupName.DistinguishedName] = $ADGroupName if ($Circular) { [Array] $NestedMembers = foreach ($Identity in $ADGroupName.Members) { if ($Script:WinADGroupMemberCache[$Identity]) { $Script:WinADGroupMemberCache[$Identity] } else { $ADObject = Get-WinADObject -Identity $Identity $Script:WinADGroupMemberCache[$Identity] = $ADObject $Script:WinADGroupMemberCache[$Identity] } } [Array] $NestedMembers = foreach ($Member in $NestedMembers) { if ($CollectedGroups -notcontains $Member.DistinguishedName) { $Member } } $Circular = $null } else { [Array] $NestedMembers = foreach ($Identity in $ADGroupName.Members) { if ($Script:WinADGroupMemberCache[$Identity]) { $Script:WinADGroupMemberCache[$Identity] } else { $ADObject = Get-WinADObject -Identity $Identity $Script:WinADGroupMemberCache[$Identity] = $ADObject $Script:WinADGroupMemberCache[$Identity] } } } if ($CountMembers) { if (-not $MembersCache[$ADGroupName.DistinguishedName]) { $DirectMembers = $NestedMembers.Where( { $_.ObjectClass -ne 'group' }, 'split') $MembersCache[$ADGroupName.DistinguishedName] = [ordered] @{DirectMembers = $DirectMembers[0] DirectMembersCount = ($DirectMembers[0]).Count DirectGroups = $DirectMembers[1] DirectGroupsCount = ($DirectMembers[1]).Count IndirectMembers = [System.Collections.Generic.List[PSCustomObject]]::new() IndirectMembersCount = $null IndirectGroups = [System.Collections.Generic.List[PSCustomObject]]::new() IndirectGroupsCount = $null } } } foreach ($NestedMember in $NestedMembers) { $DomainParentGroup = ConvertFrom-DistinguishedName -DistinguishedName $ADGroupName.DistinguishedName -ToDomainCN $CreatedObject = [ordered] @{GroupName = $InitialGroup.GroupName Name = $NestedMember.name SamAccountName = $NestedMember.SamAccountName DomainName = $NestedMember.DomainName DisplayName = $NestedMember.DisplayName Enabled = $NestedMember.Enabled Type = $NestedMember.ObjectClass DirectMembers = 0 DirectGroups = 0 IndirectMembers = 0 TotalMembers = 0 Nesting = $Nesting Circular = $false TrustedDomain = $false ParentGroup = $ADGroupName.name ParentGroupDomain = $DomainParentGroup GroupDomainName = $InitialGroup.DomainName DistinguishedName = $NestedMember.DistinguishedName Sid = $NestedMember.ObjectSID } if ($NestedMember.ObjectClass -eq "group") { if ($ADGroupName.memberof -contains $NestedMember.DistinguishedName) { $Circular = $ADGroupName.DistinguishedName $CreatedObject['Circular'] = $true } $CollectedGroups.Add($ADGroupName.DistinguishedName) if ($All) { [PSCustomObject] $CreatedObject } $OutputFromGroup = Get-WinADGroupMember -GroupName $NestedMember -Nesting $Nesting -Circular $Circular -InitialGroup $InitialGroup -CollectedGroups $CollectedGroups -Nested -All:$All.IsPresent -CountMembers:$CountMembers.IsPresent $OutputFromGroup if ($CountMembers) { foreach ($Member in $OutputFromGroup) { if ($Member.Type -eq 'group') { $MembersCache[$ADGroupName.DistinguishedName]['IndirectGroups'].Add($Member) } else { $MembersCache[$ADGroupName.DistinguishedName]['IndirectMembers'].Add($Member) } } } } else { [PSCustomObject] $CreatedObject } } } } } End { if ($Output.Count -gt 0) { if ($Nesting -eq 0) { if (-not $All) { $Output | Sort-Object -Unique -Property DistinguishedName } else { if ($AddSelf) { $InitialGroup.DirectMembers = $MembersCache[$InitialGroup.DistinguishedName].DirectMembersCount $InitialGroup.DirectGroups = $MembersCache[$InitialGroup.DistinguishedName].DirectGroupsCount foreach ($Group in $MembersCache[$InitialGroup.DistinguishedName].DirectGroups) { $InitialGroup.IndirectMembers = $MembersCache[$InitialGroup.DistinguishedName].DirectMembersCount + $InitialGroup.IndirectMembers } $AllMembersForGivenGroup = @(foreach ($Group in $MembersCache[$InitialGroup.DistinguishedName].DirectGroups) { $MembersCache[$Group.DistinguishedName].DirectMembers } $MembersCache[$InitialGroup.DistinguishedName].DirectMembers $MembersCache[$InitialGroup.DistinguishedName].IndirectMembers) $InitialGroup.TotalMembers = @($AllMembersForGivenGroup | Sort-Object -Unique -Property DistinguishedName).Count [PSCustomObject] $InitialGroup } foreach ($Object in $Output) { if ($Object.Type -eq 'group') { $Object.DirectMembers = $MembersCache[$Object.DistinguishedName].DirectMembersCount $Object.DirectGroups = $MembersCache[$Object.DistinguishedName].DirectGroupsCount foreach ($Group in $MembersCache[$Object.DistinguishedName].DirectGroups) { $Object.IndirectMembers = $MembersCache[$Group.DistinguishedName].DirectMembersCount + $Object.IndirectMembers } $AllMembersForGivenGroup = @(foreach ($Group in $MembersCache[$Object.DistinguishedName].DirectGroups) { $MembersCache[$Group.DistinguishedName].DirectMembers } $MembersCache[$Object.DistinguishedName].DirectMembers $MembersCache[$Object.DistinguishedName].IndirectMembers) $Object.TotalMembers = @($AllMembersForGivenGroup | Sort-Object -Unique -Property DistinguishedName).Count $Object } else { $Object } } } } else { $Output } } } } function Get-WinADLastBackup { <# .SYNOPSIS Gets Active directory forest or domain last backup time .DESCRIPTION Gets Active directory forest or domain last backup time .PARAMETER Domain Optionally you can pass Domains by hand .EXAMPLE $LastBackup = Get-WinADLastBackup $LastBackup | Format-Table -AutoSize .EXAMPLE $LastBackup = Get-WinADLastBackup -Domain 'ad.evotec.pl' $LastBackup | Format-Table -AutoSize .NOTES General notes #> [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation) $NameUsed = [System.Collections.Generic.List[string]]::new() [DateTime] $CurrentDate = Get-Date $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] try { [string[]]$Partitions = (Get-ADRootDSE -Server $QueryServer -ErrorAction Stop).namingContexts [System.DirectoryServices.ActiveDirectory.DirectoryContextType] $contextType = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain [System.DirectoryServices.ActiveDirectory.DirectoryContext] $context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext($contextType, $Domain) [System.DirectoryServices.ActiveDirectory.DomainController] $domainController = [System.DirectoryServices.ActiveDirectory.DomainController]::FindOne($context) } catch { Write-Warning "Get-WinADLastBackup - Failed to gather partitions information for $Domain with error $($_.Exception.Message)" } $Output = ForEach ($Name in $Partitions) { if ($NameUsed -contains $Name) { continue } else { $NameUsed.Add($Name) } $domainControllerMetadata = $domainController.GetReplicationMetadata($Name) $dsaSignature = $domainControllerMetadata.Item("dsaSignature") try { $LastBackup = [DateTime] $($dsaSignature.LastOriginatingChangeTime) } catch { $LastBackup = [DateTime]::MinValue } [PSCustomObject] @{Domain = $Domain NamingContext = $Name LastBackup = $LastBackup LastBackupDaysAgo = - (Convert-TimeToDays -StartTime ($CurrentDate) -EndTime ($LastBackup)) } } $Output } } function Get-WinADLDAPBindingsSummary { [cmdletbinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [int] $Days = 1, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation $Events = Get-Events -LogName 'Directory Service' -ID 2887 -Machine $ForestInformation.ForestDomainControllers.HostName -DateFrom ((Get-Date).Date.adddays(-$Days)) foreach ($Event in $Events) { [PSCustomobject] @{'Domain Controller' = $Event.Computer 'Date' = $Event.Date 'Number of simple binds performed without SSL/TLS' = $Event.'NoNameA0' 'Number of Negotiate/Kerberos/NTLM/Digest binds performed without signing' = $Event.'NoNameA1' 'GatheredFrom' = $Event.'GatheredFrom' 'GatheredLogName' = $Event.'GatheredLogName' } } } function Get-WinADLMSettings { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'DomainController')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [int] $Days = 1, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation foreach ($ComputerName in $ForestInformation.ForestDomainControllers.HostName) { $LSA = Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Control\Lsa' -ComputerName $ComputerName if ($Lsa -and $Lsa.PSError -eq $false) { if ($LSA.lmcompatibilitylevel) { $LMCompatibilityLevel = $LSA.lmcompatibilitylevel } else { $LMCompatibilityLevel = 3 } $LM = @{0 = 'Server sends LM and NTLM response and never uses extended session security. Clients use LM and NTLM authentication, and never use extended session security. DCs accept LM, NTLM, and NTLM v2 authentication.' 1 = 'Servers use NTLM v2 session security if it is negotiated. Clients use LM and NTLM authentication and use extended session security if the server supports it. DCs accept LM, NTLM, and NTLM v2 authentication.' 2 = 'Server sends NTLM response only. Clients use only NTLM authentication and use extended session security if the server supports it. DCs accept LM, NTLM, and NTLM v2 authentication.' 3 = 'Server sends NTLM v2 response only. Clients use NTLM v2 authentication and use extended session security if the server supports it. DCs accept LM, NTLM, and NTLM v2 authentication.' 4 = 'DCs refuse LM responses. Clients use NTLM authentication and use extended session security if the server supports it. DCs refuse LM authentication but accept NTLM and NTLM v2 authentication.' 5 = 'DCs refuse LM and NTLM responses, and accept only NTLM v2. Clients use NTLM v2 authentication and use extended session security if the server supports it. DCs refuse NTLM and LM authentication, and accept only NTLM v2 authentication.' } [PSCustomObject] @{ComputerName = $ComputerName LSAProtectionCredentials = [bool] $LSA.RunAsPPL Level = $LMCompatibilityLevel LevelDescription = $LM[$LMCompatibilityLevel] EveryoneIncludesAnonymous = [bool] $LSA.everyoneincludesanonymous LimitBlankPasswordUse = [bool] $LSA.LimitBlankPasswordUse NoLmHash = [bool] $LSA.NoLmHash DisableDomainCreds = [bool] $LSA.disabledomaincreds ForceGuest = [bool] $LSA.forceguest RestrictAnonymous = [bool] $LSA.restrictanonymous RestrictAnonymousSAM = [bool] $LSA.restrictanonymoussam SecureBoot = [bool] $LSA.SecureBoot LsaCfgFlagsDefault = $LSA.LsaCfgFlagsDefault LSAPid = $LSA.LSAPid AuditBaseDirectories = [bool] $LSA.auditbasedirectories AuditBaseObjects = [bool] $LSA.auditbaseobjects CrashOnAuditFail = $LSA.CrashOnAuditFail } } else { [PSCustomObject] @{ComputerName = $ComputerName LSAProtectionCredentials = $null Level = $null LevelDescription = $null EveryoneIncludesAnonymous = $null LimitBlankPasswordUse = $null NoLmHash = $null DisableDomainCreds = $null ForceGuest = $null RestrictAnonymous = $null RestrictAnonymousSAM = $null SecureBoot = $null LsaCfgFlagsDefault = $null LSAPid = $null AuditBaseDirectories = $null AuditBaseObjects = $null CrashOnAuditFail = $null } } } } function Get-WinADObject { [cmdletBinding()] param([Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [Array] $Identity, [alias('DomainName', 'Domain')][string] $DomainDistinguishedName, [pscredential] $Credential, [switch] $IncludeDeletedObjects) Begin { Add-Type -AssemblyName System.DirectoryServices.AccountManagement if ($DomainName) { $Context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain', $DomainName) } else { $Context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain') } } process { foreach ($Ident in $Identity) { if ($Ident.DistinguishedName) { $Ident = $Ident.DistinguishedName } $Search = [System.DirectoryServices.DirectorySearcher]::new() $Search.SizeLimit = $SizeLimit $Search.SearchRoot = $DomainDistinguishedName $IdentityGUID = "" Try { ([System.Guid]$Ident).ToByteArray() | ForEach-Object { $IdentityGUID += $("\{0:x2}" -f $_) } } Catch { $IdentityGUID = "null" } if ($PSBoundParameters['Identity']) { if ($PSBoundParameters['DeletedOnly']) { $Search.filter = "(&(isDeleted=True)(|(DistinguishedName=$Ident)(Name=$Ident)(SamAccountName=$Ident)(UserPrincipalName=$Ident)(objectGUID=$IdentityGUID)(objectSid=$Ident)))" } else { $Search.filter = "(|(DistinguishedName=$Ident)(Name=$Ident)(SamAccountName=$Ident)(UserPrincipalName=$Ident)(objectGUID=$IdentityGUID)(objectSid=$Ident))" } } if ($PSBoundParameters['DomainDistinguishedName']) { if ($DomainDistinguishedName -notlike "LDAP://*") { $DomainDistinguishedName = "LDAP://$DomainDistinguishedName" } Write-Verbose -Message "Different Domain specified: $DomainDistinguishedName" $Search.SearchRoot = $DomainDistinguishedName } if ($PSBoundParameters['Credential']) { $Cred = [System.DirectoryServices.DirectoryEntry]::new($DomainDistinguishedName, $($Credential.UserName), $($Credential.GetNetworkCredential().password)) $Search.SearchRoot = $Cred } if ($PSBoundParameters['IncludeDeletedObjects']) { $Search.Tombstone = $true } foreach ($Object in $($Search.FindAll())) { $UAC = Convert-UserAccountControl -UserAccountControl ($Object.properties.useraccountcontrol -as [string]) $ObjectClass = ($Object.properties.objectclass -as [array])[-1] $Members = $Object.properties.member -as [array] if ($ObjectClass -eq 'group') { $GroupMembers = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context, $Ident).Members.DistinguishedName if ($GroupMembers.Count -ne $Members.Count) {} $Members = $GroupMembers } $DomainName = ConvertFrom-DistinguishedName -DistinguishedName ($Object.properties.distinguishedname -as [string]) -ToDomainCN [PSCustomObject] @{DisplayName = $Object.properties.displayname -as [string] Name = $Object.properties.name -as [string] SamAccountName = $Object.properties.samaccountname -as [string] ObjectClass = $ObjectClass Enabled = if ($ObjectClass -eq 'group') { $null } else { $UAC -notcontains 'ACCOUNTDISABLE' } PasswordNeverExpire = $UAC -contains 'DONT_EXPIRE_PASSWORD' Description = $Object.properties.description -as [string] DomainName = $DomainName Distinguishedname = $Object.properties.distinguishedname -as [string] Lastlogon = $Object.properties.lastlogon -as [string] WhenCreated = $Object.properties.whencreated -as [string] WhenChanged = $Object.properties.whenchanged -as [string] Deleted = $Object.properties.isDeleted -as [string] Recycled = $Object.properties.isRecycled -as [string] UserPrincipalName = $Object.properties.userprincipalname -as [string] ObjectSID = [System.Security.Principal.SecurityIdentifier]::new($Object.Properties.objectsid[0], 0).Value MemberOf = $Object.properties.memberof -as [array] Members = $Members DirectReports = $Object.Properties.directreports } } } } } Function Get-WinADPrivilegedObjects { [alias('Get-WinADPriviligedObjects')] [cmdletbinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $LegitimateOnly, [switch] $OrphanedOnly, [switch] $SummaryOnly, [switch] $DoNotShowCriticalSystemObjects, [alias('Display')][switch] $Formatted, [string] $Splitter = [System.Environment]::NewLine, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $Domains = $ForestInformation.Domains $UsersWithAdminCount = foreach ($Domain in $Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] if ($DoNotShowCriticalSystemObjects) { $Objects = Get-ADObject -Filter 'admincount -eq 1 -and iscriticalsystemobject -notlike "*"' -Server $QueryServer -Properties whenchanged, whencreated, admincount, isCriticalSystemObject, samaccountname, "msDS-ReplAttributeMetaData" } else { $Objects = Get-ADObject -Filter 'admincount -eq 1' -Server $QueryServer -Properties whenchanged, whencreated, admincount, isCriticalSystemObject, samaccountname, "msDS-ReplAttributeMetaData" } foreach ($_ in $Objects) { [PSCustomObject] @{Domain = $Domain distinguishedname = $_.distinguishedname whenchanged = $_.whenchanged whencreated = $_.whencreated admincount = $_.admincount SamAccountName = $_.SamAccountName objectclass = $_.objectclass isCriticalSystemObject = if ($_.isCriticalSystemObject) { $true } else { $false } adminCountDate = ($_.'msDS-ReplAttributeMetaData' | ForEach-Object { ([XML]$_.Replace("`0", "")).DS_REPL_ATTR_META_DATA | Where-Object { $_.pszAttributeName -eq "admincount" } }).ftimeLastOriginatingChange | Get-Date -Format MM/dd/yyyy } } } $CriticalGroups = foreach ($Domain in $Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] Get-ADGroup -Filter 'admincount -eq 1 -and iscriticalsystemobject -eq $true' -Server $QueryServer | Select-Object @{name = 'Domain'; expression = { $domain } }, distinguishedname } $AdminCountAll = foreach ($object in $UsersWithAdminCount) { $DistinguishedName = $object.distinguishedname $IsMember = foreach ($Group in $CriticalGroups) { $QueryServer = $ForestInformation['QueryServers']["$($Group.Domain)"].HostName[0] $Group = Get-ADGroup -Filter "Member -RecursiveMatch `$DistinguishedName" -SearchBase $Group.DistinguishedName -Server $QueryServer if ($Group) { $Group.DistinguishedName } } if ($IsMember.Count -gt 0) { $GroupDomains = $IsMember } else { $GroupDomains = $null } if ($Formatted) { $GroupDomains = $GroupDomains -join $Splitter $User = [PSCustomObject] @{DistinguishedName = $Object.DistinguishedName Domain = $Object.domain IsOrphaned = (-not $Object.isCriticalSystemObject) -and (-not $IsMember -and -not $Object.isCriticalSystemObject) IsMember = $IsMember.Count -gt 0 IsCriticalSystemObject = $Object.isCriticalSystemObject Admincount = $Object.admincount AdminCountDate = $Object.adminCountDate WhenCreated = $Object.whencreated ObjectClass = $Object.objectclass GroupDomain = $GroupDomains } } else { $User = [PSCustomObject] @{'DistinguishedName' = $Object.DistinguishedName 'Domain' = $Object.domain 'IsOrphaned' = (-not $Object.isCriticalSystemObject) -and (-not $IsMember -and -not $Object.isCriticalSystemObject) 'IsMember' = $IsMember.Count -gt 0 'IsCriticalSystemObject' = $Object.isCriticalSystemObject 'AdminCount' = $Object.admincount 'AdminCountDate' = $Object.adminCountDate 'WhenCreated' = $Object.whencreated 'ObjectClass' = $Object.objectclass 'GroupDomain' = $GroupDomains } } $User } $Output = @(if ($OrphanedOnly) { $AdminCountAll | Where-Object { $_.IsOrphaned } } elseif ($LegitimateOnly) { $AdminCountAll | Where-Object { $_.IsOrphaned -eq $false } } else { $AdminCountAll }) if ($SummaryOnly) { $Output | Group-Object ObjectClass | Select-Object -Property Name, Count } else { $Output } } function Get-WinADProxyAddresses { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER ADUser ADUser Object .PARAMETER RemovePrefix Removes prefix from proxy address such as SMTP: or smtp: .PARAMETER ToLower Makes sure all returned data is lower case .PARAMETER Formatted Makes sure data is formatted for display, rather than for working with objects .PARAMETER Splitter Splitter or Joiner that connects data together such as an array of 3 aliases .EXAMPLE $ADUsers = Get-ADUser -Filter * -Properties ProxyAddresses foreach ($User in $ADUsers) { Get-WinADProxyAddresses -ADUser $User } .EXAMPLE $ADUsers = Get-ADUser -Filter * -Properties ProxyAddresses foreach ($User in $ADUsers) { Get-WinADProxyAddresses -ADUser $User -RemovePrefix } .NOTES General notes #> [CmdletBinding()] param([Object] $ADUser, [switch] $RemovePrefix, [switch] $ToLower, [switch] $Formatted, [alias('Joiner')][string] $Splitter = ',') $Summary = [PSCustomObject] @{EmailAddress = $ADUser.EmailAddress Primary = [System.Collections.Generic.List[string]]::new() Secondary = [System.Collections.Generic.List[string]]::new() Sip = [System.Collections.Generic.List[string]]::new() x500 = [System.Collections.Generic.List[string]]::new() Other = [System.Collections.Generic.List[string]]::new() Broken = [System.Collections.Generic.List[string]]::new() } foreach ($Proxy in $ADUser.ProxyAddresses) { if ($Proxy -like '*,*') { $Summary.Broken.Add($Proxy) } elseif ($Proxy.StartsWith('SMTP:')) { if ($RemovePrefix) { $Proxy = $Proxy -replace 'SMTP:', '' } if ($ToLower) { $Proxy = $Proxy.ToLower() } $Summary.Primary.Add($Proxy) } elseif ($Proxy.StartsWith('smtp:') -or $Proxy -notlike "*:*") { if ($RemovePrefix) { $Proxy = $Proxy -replace 'smtp:', '' } if ($ToLower) { $Proxy = $Proxy.ToLower() } $Summary.Secondary.Add($Proxy) } elseif ($Proxy.StartsWith('x500')) { if ($RemovePrefix) { $Proxy = $Proxy } if ($ToLower) { $Proxy = $Proxy.ToLower() } $Summary.x500.Add($Proxy) } elseif ($Proxy.StartsWith('sip:')) { if ($RemovePrefix) { $Proxy = $Proxy } if ($ToLower) { $Proxy = $Proxy.ToLower() } $Summary.Sip.Add($Proxy) } else { if ($RemovePrefix) { $Proxy = $Proxy } if ($ToLower) { $Proxy = $Proxy.ToLower() } $Summary.Other.Add($Proxy) } } if ($Formatted) { $Summary.Primary = $Summary.Primary -join $Splitter $Summary.Secondary = $Summary.Secondary -join $Splitter $Summary.Sip = $Summary.Sip -join $Splitter $Summary.x500 = $Summary.x500 -join $Splitter $Summary.Other = $Summary.Other -join $Splitter } $Summary } function Get-WinADSharePermission { [cmdletBinding(DefaultParameterSetName = 'Path')] param([Parameter(ParameterSetName = 'Path', Mandatory)][string] $Path, [Parameter(ParameterSetName = 'ShareType', Mandatory)][validateset('NetLogon', 'SYSVOL')][string[]] $ShareType, [switch] $Owner, [string[]] $Name, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation) if ($ShareType) { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $Path = -join ("\\", $Domain, "\$ShareType") @(Get-Item -Path $Path -Force) + @(Get-ChildItem -Path $Path -Recurse:$true -Force -ErrorAction SilentlyContinue -ErrorVariable Err) | ForEach-Object -Process { if ($Owner) { $Output = Get-FileOwner -JustPath -Path $_ -Resolve -AsHashTable $Output['Attributes'] = $_.Attributes [PSCustomObject] $Output } else { $Output = Get-FilePermission -Path $_ -ResolveTypes -Extended -AsHashTable foreach ($O in $Output) { $O['Attributes'] = $_.Attributes [PSCustomObject] $O } } } } } else { if ($Path -and (Test-Path -Path $Path)) { @(Get-Item -Path $Path -Force) + @(Get-ChildItem -Path $Path -Recurse:$true -Force -ErrorAction SilentlyContinue -ErrorVariable Err) | ForEach-Object -Process { if ($Owner) { $Output = Get-FileOwner -JustPath -Path $_ -Resolve -AsHashTable $Output['Attributes'] = $_.Attributes [PSCustomObject] $Output } else { $Output = Get-FilePermission -Path $_ -ResolveTypes -Extended -AsHashTable foreach ($O in $Output) { $O['Attributes'] = $_.Attributes [PSCustomObject] $O } } } } } foreach ($e in $err) { Write-Warning "Get-WinADSharePermission - $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } } function Get-WinADSiteConnections { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [alias('Joiner')][string] $Splitter, [switch] $Formatted, [System.Collections.IDictionary] $ExtendedForestInformation) [Flags()] enum ConnectionOption { None IsGenerated TwoWaySync OverrideNotifyDefault = 4 UseNotify = 8 DisableIntersiteCompression = 16 UserOwnedSchedule = 32 RodcTopology = 64 } $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation $QueryServer = $ForestInformation['QueryServers'][$($ForestInformation.Forest.Name)]['HostName'][0] $NamingContext = (Get-ADRootDSE -Server $QueryServer).configurationNamingContext $Connections = Get-ADObject –SearchBase $NamingContext -LDAPFilter "(objectCategory=ntDSConnection)" -Properties * -Server $QueryServer $FormmatedConnections = foreach ($_ in $Connections) { if ($null -eq $_.Options) { $Options = 'None' } else { $Options = ([ConnectionOption] $_.Options) -split ', ' } if ($Formatted) { $Dictionary = [PSCustomObject] @{'CN' = $_.CN 'Description' = $_.Description 'Display Name' = $_.DisplayName 'Enabled Connection' = $_.enabledConnection 'Server From' = if ($_.fromServer -match '(?<=CN=NTDS Settings,CN=)(.*)(?=,CN=Servers,)') { $Matches[0] } else { $_.fromServer } 'Server To' = if ($_.DistinguishedName -match '(?<=CN=NTDS Settings,CN=)(.*)(?=,CN=Servers,)') { $Matches[0] } else { $_.fromServer } 'Site From' = if ($_.fromServer -match '(?<=,CN=Servers,CN=)(.*)(?=,CN=Sites,CN=Configuration)') { $Matches[0] } else { $_.fromServer } 'Site To' = if ($_.DistinguishedName -match '(?<=,CN=Servers,CN=)(.*)(?=,CN=Sites,CN=Configuration)') { $Matches[0] } else { $_.fromServer } 'Options' = if ($Splitter -ne '') { $Options -Join $Splitter } else { $Options } 'When Created' = $_.WhenCreated 'When Changed' = $_.WhenChanged 'Is Deleted' = $_.IsDeleted } } else { $Dictionary = [PSCustomObject] @{CN = $_.CN Description = $_.Description DisplayName = $_.DisplayName EnabledConnection = $_.enabledConnection ServerFrom = if ($_.fromServer -match '(?<=CN=NTDS Settings,CN=)(.*)(?=,CN=Servers,)') { $Matches[0] } else { $_.fromServer } ServerTo = if ($_.DistinguishedName -match '(?<=CN=NTDS Settings,CN=)(.*)(?=,CN=Servers,)') { $Matches[0] } else { $_.fromServer } SiteFrom = if ($_.fromServer -match '(?<=,CN=Servers,CN=)(.*)(?=,CN=Sites,CN=Configuration)') { $Matches[0] } else { $_.fromServer } SiteTo = if ($_.DistinguishedName -match '(?<=,CN=Servers,CN=)(.*)(?=,CN=Sites,CN=Configuration)') { $Matches[0] } else { $_.fromServer } Options = if ($Splitter -ne '') { $Options -Join $Splitter } else { $Options } WhenCreated = $_.WhenCreated WhenChanged = $_.WhenChanged IsDeleted = $_.IsDeleted } } $Dictionary } $FormmatedConnections } function Get-WinADSiteLinks { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [alias('Joiner')][string] $Splitter, [string] $Formatted, [System.Collections.IDictionary] $ExtendedForestInformation) [Flags()] enum SiteLinksOptions { None = 0 UseNotify = 1 TwoWaySync = 2 DisableCompression = 4 } $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0] $NamingContext = (Get-ADRootDSE -Server $QueryServer).configurationNamingContext $SiteLinks = Get-ADObject -LDAPFilter "(objectCategory=sitelink)" –SearchBase $NamingContext -Properties * -Server $QueryServer foreach ($_ in $SiteLinks) { if ($null -eq $_.Options) { $Options = 'None' } else { $Options = ([SiteLinksOptions] $_.Options) -split ', ' } if ($Formatted) { [PSCustomObject] @{Name = $_.CN Cost = $_.Cost 'Replication Frequency In Minutes' = $_.ReplInterval Options = if ($Splitter -ne '') { $Options -Join $Splitter } else { $Options } Created = $_.WhenCreated Modified = $_.WhenChanged 'Protected From Accidental Deletion' = $_.ProtectedFromAccidentalDeletion } } else { [PSCustomObject] @{Name = $_.CN Cost = $_.Cost ReplicationFrequencyInMinutes = $_.ReplInterval Options = if ($Splitter -ne '') { $Options -Join $Splitter } else { $Options } Created = $_.WhenCreated Modified = $_.WhenChanged ProtectedFromAccidentalDeletion = $_.ProtectedFromAccidentalDeletion } } } } function Get-WinADTomebstoneLifetime { [Alias('Get-WinADForestTomebstoneLifetime')] [CmdletBinding()] param([alias('ForestName')][string] $Forest, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0] $RootDSE = Get-ADRootDSE -Server $QueryServer $Output = (Get-ADObject -Server $QueryServer -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$(($RootDSE).configurationNamingContext)" -Properties tombstoneLifetime) if ($null -eq $Output) { [PSCustomObject] @{TombstoneLifeTime = 60 } } else { [PSCustomObject] @{TombstoneLifeTime = $Output.tombstoneLifetime } } } function Get-WinADTrusts { [CmdletBinding()] param([string] $Forest, [alias('Domain')][string[]] $IncludeDomains, [string[]] $ExcludeDomains, [switch] $Display, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $Trusts = Get-ADTrust -Server $QueryServer -Filter * -Properties * $DomainPDC = $ForestInformation['DomainDomainControllers'][$Domain] | Where-Object { $_.IsPDC -eq $true } $PropertiesTrustWMI = @('FlatName', 'SID', 'TrustAttributes', 'TrustDirection', 'TrustedDCName', 'TrustedDomain', 'TrustIsOk', 'TrustStatus', 'TrustStatusString', 'TrustType') $TrustStatatuses = Get-CimInstance -ClassName Microsoft_DomainTrustStatus -Namespace root\MicrosoftActiveDirectory -ComputerName $DomainPDC.HostName -ErrorAction SilentlyContinue -Verbose:$false -Property $PropertiesTrustWMI $ReturnData = foreach ($Trust in $Trusts) { $TrustWMI = $TrustStatatuses | & { process { if ($_.TrustedDomain -eq $Trust.Target) { $_ } } } if ($Display) { [PsCustomObject] @{'Trust Source' = $Domain 'Trust Target' = $Trust.Target 'Trust Direction' = $Trust.Direction 'Trust Attributes' = if ($Trust.TrustAttributes -is [int]) { Get-ADTrustAttributes -Value $Trust.TrustAttributes } else { 'Error - needs fixing' } 'Trust Status' = if ($null -ne $TrustWMI) { $TrustWMI.TrustStatusString } else { 'N/A' } 'Forest Transitive' = $Trust.ForestTransitive 'Selective Authentication' = $Trust.SelectiveAuthentication 'SID Filtering Forest Aware' = $Trust.SIDFilteringForestAware 'SID Filtering Quarantined' = $Trust.SIDFilteringQuarantined 'Disallow Transivity' = $Trust.DisallowTransivity 'Intra Forest' = $Trust.IntraForest 'Is Tree Parent' = $Trust.IsTreeParent 'Is Tree Root' = $Trust.IsTreeRoot 'TGTDelegation' = $Trust.TGTDelegation 'TrustedPolicy' = $Trust.TrustedPolicy 'TrustingPolicy' = $Trust.TrustingPolicy 'TrustType' = $Trust.TrustType 'UplevelOnly' = $Trust.UplevelOnly 'UsesAESKeys' = $Trust.UsesAESKeys 'UsesRC4Encryption' = $Trust.UsesRC4Encryption 'Trust Source DC' = if ($null -ne $TrustWMI) { $TrustWMI.PSComputerName } else { '' } 'Trust Target DC' = if ($null -ne $TrustWMI) { $TrustWMI.TrustedDCName.Replace('\\', '') } else { '' } 'Trust Source DN' = $Trust.Source 'ObjectGUID' = $Trust.ObjectGUID 'Created' = $Trust.Created 'Modified' = $Trust.Modified 'Deleted' = $Trust.Deleted 'SID' = $Trust.securityIdentifier 'TrustOK' = if ($null -ne $TrustWMI) { $TrustWMI.TrustIsOK } else { $false } 'TrustStatus' = if ($null -ne $TrustWMI) { $TrustWMI.TrustStatus } else { -1 } } } else { [PsCustomObject] @{'TrustSource' = $Domain 'TrustTarget' = $Trust.Target 'TrustDirection' = $Trust.Direction 'TrustAttributes' = if ($Trust.TrustAttributes -is [int]) { Get-ADTrustAttributes -Value $Trust.TrustAttributes } else { 'Error - needs fixing' } 'TrustStatus' = if ($null -ne $TrustWMI) { $TrustWMI.TrustStatusString } else { 'N/A' } 'ForestTransitive' = $Trust.ForestTransitive 'SelectiveAuthentication' = $Trust.SelectiveAuthentication 'SIDFiltering Forest Aware' = $Trust.SIDFilteringForestAware 'SIDFiltering Quarantined' = $Trust.SIDFilteringQuarantined 'DisallowTransivity' = $Trust.DisallowTransivity 'IntraForest' = $Trust.IntraForest 'IsTreeParent' = $Trust.IsTreeParent 'IsTreeRoot' = $Trust.IsTreeRoot 'TGTDelegation' = $Trust.TGTDelegation 'TrustedPolicy' = $Trust.TrustedPolicy 'TrustingPolicy' = $Trust.TrustingPolicy 'TrustType' = $Trust.TrustType 'UplevelOnly' = $Trust.UplevelOnly 'UsesAESKeys' = $Trust.UsesAESKeys 'UsesRC4Encryption' = $Trust.UsesRC4Encryption 'TrustSourceDC' = if ($null -ne $TrustWMI) { $TrustWMI.PSComputerName } else { '' } 'TrustTargetDC' = if ($null -ne $TrustWMI) { $TrustWMI.TrustedDCName.Replace('\\', '') } else { '' } 'TrustSourceDN' = $Trust.Source 'ObjectGUID' = $Trust.ObjectGUID 'Created' = $Trust.Created 'Modified' = $Trust.Modified 'Deleted' = $Trust.Deleted 'SID' = $Trust.securityIdentifier 'TrustOK' = if ($null -ne $TrustWMI) { $TrustWMI.TrustIsOK } else { $false } 'TrustStatusInt' = if ($null -ne $TrustWMI) { $TrustWMI.TrustStatus } else { -1 } } } } $ReturnData } } function Get-WinADUserPrincipalName { [cmdletbinding()] param([Parameter(Mandatory = $true)][Object] $User, [Parameter(Mandatory = $true)][string] $DomainName, [switch] $ReplaceDomain, [switch] $NameSurname, [switch] $FixLatinChars, [switch] $ToLower) if ($User.UserPrincipalName) { $NewUserName = $User.UserPrincipalName if ($ReplaceDomain) { $NewUserName = ($User.UserPrincipalName -split '@')[0] $NewUserName = -join ($NewUserName, '@', $DomainName) } if ($NameSurname) { if ($User.GivenName -and $User.Surname) { $NewUsername = -join ($User.GivenName, '.', $User.Surname, '@', $DomainName) } else { Write-Warning "Get-WinADUserPrincipalName - UserPrincipalName couldn't be changed to GivenName.SurName@$DomainName" } } if ($FixLatinChars) { $NewUsername = Remove-StringLatinCharacters -String $NewUsername } if ($ToLower) { $NewUsername = $NewUserName.ToLower() } if ($NewUserName -eq $User.UserPrincipalName) { Write-Warning "Get-WinADUserPrincipalName - UserPrincipalName didn't change. Stays as $NewUserName" } $NewUsername } } function Get-WinADUsersForeignSecurityPrincipalList { [alias('Get-WinADUsersFP')] param([alias('ForestName')][string] $Forest, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [string[]] $ExcludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $ForeignSecurityPrincipalList = Get-ADObject -Filter { ObjectClass -eq 'ForeignSecurityPrincipal' } -Properties * -Server $QueryServer foreach ($FSP in $ForeignSecurityPrincipalList) { Try { $Translated = (([System.Security.Principal.SecurityIdentifier]::new($FSP.objectSid)).Translate([System.Security.Principal.NTAccount])).Value } Catch { $Translated = $null } Add-Member -InputObject $FSP -Name 'TranslatedName' -Value $Translated -MemberType NoteProperty -Force } $ForeignSecurityPrincipalList } } function Get-WinADWellKnownFolders { [cmdletBinding()] param([string] $Forest, [alias('Domain')][string[]] $IncludeDomains, [string[]] $ExcludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $AsCustomObject) $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $DomainInformation = Get-ADDomain -Server $Domain $WellKnownFolders = $DomainInformation | Select-Object -Property UsersContainer, ComputersContainer, DomainControllersContainer, DeletedObjectsContainer, SystemsContainer, LostAndFoundContainer, QuotasContainer, ForeignSecurityPrincipalsContainer $CurrentWellKnownFolders = [ordered] @{} foreach ($_ in $WellKnownFolders.PSObject.Properties.Name) { $CurrentWellKnownFolders[$_] = $DomainInformation.$_ } if ($AsHashtable) { $CurrentWellKnownFolders } else { [PSCustomObject] $CurrentWellKnownFolders } } } function Remove-ADACL { [cmdletBinding(SupportsShouldProcess)] param([Array] $ACL, [string] $Principal, [System.DirectoryServices.ActiveDirectoryRights] $AccessRule, [System.Security.AccessControl.AccessControlType] $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow) foreach ($SubACL in $ACL) { $OutputRequiresCommit = @(if ($Principal -like '*-*-*-*') { $Identity = [System.Security.Principal.SecurityIdentifier]::new($Principal) } else { [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal) } if (-not $AccessRule) { Write-Verbose "Remove-ADACL - Removing access for $($Identity) / All Rights" try { $SubACL.ACL.RemoveAccess($Identity, $AccessControlType) $true } catch { Write-Warning "Remove-ADACL - Removing permissions for $($SubACL.DistinguishedName) failed: $($_.Exception.Message)" $false } } else { foreach ($Rule in $AccessRule) { $AccessRuleToRemove = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $Rule, $AccessControlType) Write-Verbose "Remove-ADACL - Removing access for $($AccessRuleToRemove.IdentityReference) / $($AccessRuleToRemove.ActiveDirectoryRights)" $SubACL.ACL.RemoveAccessRule($AccessRuleToRemove) } }) if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) { Write-Verbose "Remove-ADACL - Saving permissions for $($SubACL.DistinguishedName)" Set-Acl -Path $SubACL.Path -AclObject $SubACL.ACL -ErrorAction Stop } elseif ($OutputRequiresCommit -contains $false) { Write-Warning "Remove-ADACL - Skipping saving permissions for $($SubACL.DistinguishedName) due to errors." } } } function Remove-WinADDuplicateObject { [cmdletBinding(SupportsShouldProcess)] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $CNF = Get-ADObject -LDAPFilter "(|(cn=*\0ACNF:*)(ou=*OACNF:*))" -SearchScope Subtree -Server $QueryServer foreach ($_ in $CNF) { try { Remove-ADObject -Identity $_ -Recursive } catch { Write-Warning "Remove-WinADDuplicateObjects - Failed for $($_.DistinguishedName) with error: $($_.Exception.Message)" } } } } function Remove-WinADSharePermission { [cmdletBinding(DefaultParameterSetName = 'Path', SupportsShouldProcess)] param([Parameter(ParameterSetName = 'Path', Mandatory)][string] $Path, [ValidateSet('Unknown')][string] $Type = 'Unknown', [int] $LimitProcessing) Begin { [int] $Count = 0 } Process { if ($Path -and (Test-Path -Path $Path)) { $Data = @(Get-Item -Path $Path) + @(Get-ChildItem -Path $Path -Recurse:$true) foreach ($_ in $Data) { $PathToProcess = $_.FullName $Permissions = Get-FilePermission -Path $PathToProcess -Extended -IncludeACLObject -ResolveTypes $OutputRequiresCommit = foreach ($Permission in $Permissions) { if ($Type -eq 'Unknown' -and $Permission.PrincipalType -eq 'Unknown' -and $Permission.IsInherited -eq $false) { try { Write-Verbose "Remove-WinADSharePermission - Removing permissions from $PathToProcess for $($Permission.Principal) / $($Permission.PrincipalType)" $Permission.AllACL.RemoveAccessRule($Permission.ACL) $true } catch { Write-Warning "Remove-WinADSharePermission - Removing permissions from $PathToProcess for $($Permission.Principal) / $($Permission.PrincipalType) failed: $($_.Exception.Message)" $false } } } if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) { try { Set-Acl -Path $PathToProcess -AclObject $Permissions[0].ALLACL -ErrorAction Stop } catch { Write-Warning "Remove-WinADSharePermission - Commit for $($PathToProcess) failed: $($_.Exception.Message)" } $Count++ if ($Count -eq $LimitProcessing) { break } } } } } End {} } function Rename-WinADUserPrincipalName { [cmdletbinding()] param([Parameter(Mandatory = $true)][Array] $Users, [Parameter(Mandatory = $true)][string] $DomainName, [switch] $ReplaceDomain, [switch] $NameSurname, [switch] $FixLatinChars, [switch] $ToLower, [switch] $WhatIf) foreach ($User in $Users) { $NewUserPrincipalName = Get-WinADUserPrincipalName -User $User -DomainName $DomainName -ReplaceDomain:$ReplaceDomain -NameSurname:$NameSurname -FixLatinChars:$FixLatinChars -ToLower:$ToLower if ($NewUserPrincipalName -ne $User.UserPrincipalName) { Set-ADUser -Identity $User.DistinguishedName -UserPrincipalName $NewUserPrincipalName -WhatIf:$WhatIf } } } function Repair-WinADEmailAddress { [CmdletBinding(SupportsShouldProcess)] param([Microsoft.ActiveDirectory.Management.ADAccount] $ADUser, [string] $ToEmail, [switch] $Display) $Summary = [ordered] @{SamAccountName = $ADUser.SamAccountName UserPrincipalName = $ADUser.UserPrincipalName EmailAddress = '' ProxyAddresses = '' EmailAddressStatus = 'Not required' ProxyAddressesStatus = 'Not required' EmailAddressError = '' ProxyAddressesError = '' } $RequiredProperties = @('EmailAddress' 'proxyAddresses') foreach ($Property in $RequiredProperties) { if ($ADUser.PSObject.Properties.Name -notcontains $Property) { Write-Warning "Repair-WinADEmailAddress - Property EmailAddress and ProxyAddresses are required. Try again." return } } $ProcessUser = Get-WinADProxyAddresses -ADUser $ADUser -RemovePrefix $EmailAddresses = [System.Collections.Generic.List[string]]::new() $ProxyAddresses = [System.Collections.Generic.List[string]]::new() $ExpectedUser = [ordered] @{EmailAddress = $ToEmail Primary = $ToEmail Secondary = '' Sip = $ProcessUser.Sip x500 = $ProcessUser.x500 Other = $ProcessUser.Other } $MakePrimary = "SMTP:$ToEmail" $ProxyAddresses.Add($MakePrimary) $Types = @('Sip', 'x500', 'Other') foreach ($Type in $Types) { foreach ($Address in $ExpectedUser.$Type) { $ProxyAddresses.Add($Address) } } $TypesEmails = @('Primary', 'Secondary') foreach ($Type in $TypesEmails) { foreach ($Address in $ProcessUser.$Type) { if ($Address -ne $ToEmail) { $EmailAddresses.Add($Address) } } } foreach ($Email in $EmailAddresses) { $ProxyAddresses.Add("smtp:$Email") } $Summary['EmailAddress'] = $ToEmail if ($ProcessUser.EmailAddress -ne $ExpectedUser.EmailAddress) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $ToEmail will be set in EmailAddresss field (1)")) { try { Set-ADUser -Identity $ADUser -EmailAddress $ToEmail -ErrorAction Stop $Summary['EmailAddressStatus'] = 'Success' $Summary['EmailAddressError'] = '' } catch { $Summary['EmailAddressStatus'] = 'Failed' $Summary['EmailAddressError'] = $_.Exception.Message } } else { $Summary['EmailAddressStatus'] = 'Whatif' $Summary['EmailAddressError'] = '' } } [string[]] $ExpectedProxyAddresses = ($ProxyAddresses | Sort-Object -Unique | ForEach-Object { $_ }) [string[]] $CurrentProxyAddresses = ($ADUser.ProxyAddresses | Sort-Object | ForEach-Object { $_ }) $Summary['ProxyAddresses'] = $ExpectedProxyAddresses -join ';' if (Compare-Object -ReferenceObject $ExpectedProxyAddresses -DifferenceObject $CurrentProxyAddresses) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $ExpectedProxyAddresses will replace proxy addresses (2)")) { try { Set-ADUser -Identity $ADUser -Replace @{proxyAddresses = $ExpectedProxyAddresses } -ErrorAction Stop $Summary['ProxyAddressesStatus'] = 'Success' $Summary['ProxyAddressesError'] = '' } catch { $Summary['ProxyAddressesStatus'] = 'Failed' $Summary['ProxyAddressesError'] = $_.Exception.Message } } else { $Summary['ProxyAddressesStatus'] = 'WhatIf' $Summary['ProxyAddressesError'] = '' } } if ($Display) { [PSCustomObject] $Summary } } function Set-ADACLOwner { [cmdletBinding(SupportsShouldProcess)] param([Array] $ADObject, [Parameter(Mandatory)][string] $Principal) Begin { if ($Principal -is [string]) { if ($Principal -like '*/*') { $SplittedName = $Principal -split '/' [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($SplittedName[0], $SplittedName[1]) } else { [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal) } } else { return } } Process { foreach ($Object in $ADObject) { if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) { [string] $DistinguishedName = $Object.DistinguishedName [string] $CanonicalName = $Object.CanonicalName [string] $ObjectClass = $Object.ObjectClass } elseif ($Object -is [string]) { [string] $DistinguishedName = $Object [string] $CanonicalName = '' [string] $ObjectClass = '' } else { Write-Warning "Set-ADACLOwner - Object not recognized. Skipping..." continue } $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDC) -replace '=' -replace ',' if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { Write-Verbose "Set-ADACLOwner - Enabling PSDrives for $DistinguishedName to $DNConverted" New-ADForestDrives -ForestName $ForestName if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { Write-Warning "Set-ADACLOwner - Drive $DNConverted not mapped. Terminating..." return } } $PathACL = "$DNConverted`:\$($DistinguishedName)" $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop $CurrentOwner = $ACLs.Owner Write-Verbose "Set-ADACLOwner - Changing owner from $($CurrentOwner) to $Identity for $($ACLs.Path)" try { $ACLs.SetOwner($Identity) } catch { Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $Identity for $($ACLs.Path): $($_.Exception.Message)" break } try { Set-Acl -Path $PathACL -AclObject $ACLs -ErrorAction Stop } catch { Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $Identity for $($ACLs.Path): $($_.Exception.Message)" } } } End {} } function Set-WinADDiagnostics { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [ValidateSet('Knowledge Consistency Checker (KCC)', 'Security Events', 'ExDS Interface Events', 'MAPI Interface Events', 'Replication Events', 'Garbage Collection', 'Internal Configuration', 'Directory Access', 'Internal Processing', 'Performance Counters', 'Initialization / Termination', 'Service Control', 'Name Resolution', 'Backup', 'Field Engineering', 'LDAP Interface Events', 'Setup', 'Global Catalog', 'Inter-site Messaging', 'Group Caching', 'Linked-Value Replication', 'DS RPC Client', 'DS RPC Server', 'DS Schema', 'Transformation Engine', 'Claims-Based Access Control', 'Netlogon')][string[]] $Diagnostics, [string] $Level, [System.Collections.IDictionary] $ExtendedForestInformation) $LevelsDictionary = @{'None' = 0 'Minimal' = 1 'Basic' = 2 'Extensive' = 3 'Verbose' = 4 'Internal' = 5 } $Type = @{'Knowledge Consistency Checker (KCC)' = '1 Knowledge Consistency Checker' 'Security Events' = '2 Security Events' 'ExDS Interface Events' = '3 ExDS Interface Events' 'MAPI Interface Events' = '4 MAPI Interface Events' 'Replication Events' = '5 Replication Events' 'Garbage Collection' = '6 Garbage Collection' 'Internal Configuration' = '7 Internal Configuration' 'Directory Access' = '8 Directory Access' 'Internal Processing' = '9 Internal Processing' 'Performance Counters' = '10 Performance Counters' 'Initialization / Termination' = '11 Initialization/Termination' 'Service Control' = '12 Service Control' 'Name Resolution' = '13 Name Resolution' 'Backup' = '14 Backup' 'Field Engineering' = '15 Field Engineering' 'LDAP Interface Events' = '16 LDAP Interface Events' 'Setup' = '17 Setup' 'Global Catalog' = '18 Global Catalog' 'Inter-site Messaging' = '19 Inter-site Messaging' 'Group Caching' = '20 Group Caching' 'Linked-Value Replication' = '21 Linked-Value Replication' 'DS RPC Client' = '22 DS RPC Client' 'DS RPC Server' = '23 DS RPC Server' 'DS Schema' = '24 DS Schema' 'Transformation Engine' = '25 Transformation Engine' 'Claims-Based Access Control' = '26 Claims-Based Access Control' } $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation [Array] $Computers = $ForestInformation.ForestDomainControllers.HostName foreach ($Computer in $Computers) { foreach ($D in $Diagnostics) { if ($D) { $DiagnosticsType = $Type[$D] $DiagnosticsLevel = $LevelsDictionary[$Level] if ($null -ne $DiagnosticsType -and $null -ne $DiagnosticsLevel) { Write-Verbose "Set-WinADDiagnostics - Setting $DiagnosticsType to $DiagnosticsLevel on $Computer" Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' -Type REG_DWORD -Key $DiagnosticsType -Value $DiagnosticsLevel -ComputerName $Computer } else { if ($D -eq 'Netlogon') { if ($Level -eq 'None') { Write-Verbose "Set-WinADDiagnostics - Setting Netlogon Diagnostics to Enabled on $Computer" Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -Type REG_DWORD -Key 'DbFlag' -Value 0 -ComputerName $Computer -Verbose:$false } else { Write-Verbose "Set-WinADDiagnostics - Setting Netlogon Diagnostics to Disabled on $Computer" Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -Type REG_DWORD -Key 'DbFlag' -Value 545325055 -ComputerName $Computer -Verbose:$false } } } } } } } [scriptblock] $LevelAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) @('None', 'Minimal', 'Basic', 'Extensive', 'Verbose', 'Internal') } Register-ArgumentCompleter -CommandName Set-WinADDiagnostics -ParameterName Level -ScriptBlock $LevelAutoCompleter function Set-WinADReplication { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [int] $ReplicationInterval = 15, [switch] $Instant, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation $QueryServer = $ForestInformation.QueryServers['Forest']['HostName'][0] $NamingContext = (Get-ADRootDSE -Server $QueryServer).configurationNamingContext Get-ADObject -LDAPFilter "(objectCategory=sitelink)" –SearchBase $NamingContext -Properties options, replInterval -Server $QueryServer | ForEach-Object { if ($Instant) { Set-ADObject $_ -Replace @{replInterval = $ReplicationInterval } -Server $QueryServer Set-ADObject $_ –Replace @{options = $($_.options -bor 1) } -Server $QueryServer } else { Set-ADObject $_ -Replace @{replInterval = $ReplicationInterval } -Server $QueryServer } } } function Set-WinADReplicationConnections { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [switch] $Force, [System.Collections.IDictionary] $ExtendedForestInformation) [Flags()] enum ConnectionOption { None IsGenerated TwoWaySync OverrideNotifyDefault = 4 UseNotify = 8 DisableIntersiteCompression = 16 UserOwnedSchedule = 32 RodcTopology = 64 } $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation $QueryServer = $ForestInformation.QueryServers['Forest']['HostName'][0] $NamingContext = (Get-ADRootDSE -Server $QueryServer).configurationNamingContext $Connections = Get-ADObject –SearchBase $NamingContext -LDAPFilter "(objectCategory=ntDSConnection)" -Properties * -Server $QueryServer foreach ($_ in $Connections) { $OptionsTranslated = [ConnectionOption] $_.Options if ($OptionsTranslated -like '*IsGenerated*' -and -not $Force) { Write-Verbose "Set-WinADReplicationConnections - Skipping $($_.CN) automatically generated link" } else { Write-Verbose "Set-WinADReplicationConnections - Changing $($_.CN)" Set-ADObject $_ –Replace @{options = $($_.options -bor 8) } -Server $QueryServer } } } function Set-WinADShare { [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Type')] param([string] $Path, [validateset('NetLogon')][string[]] $ShareType, [switch] $Owner, [Parameter(ParameterSetName = 'Principal', Mandatory)][string] $Principal, [Parameter(ParameterSetName = 'Type', Mandatory)] [validateset('Default')][string[]] $Type) if ($ShareType) { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $Path = -join ("\\", $Domain, "\$ShareType") @(Get-Item -Path $Path) + @(Get-ChildItem -Path $Path -Recurse:$true) | ForEach-Object -Process { if ($Owner) { Get-FileOwner -JustPath -Path $_ -Resolve } else { Get-FilePermission -Path $_ -ResolveTypes -Extended } } } } else { if ($Path -and (Test-Path -Path $Path)) { @(Get-Item -Path $Path) + @(Get-ChildItem -Path $Path -Recurse:$true) | ForEach-Object -Process { if ($Owner) { $IdentityOwner = Get-FileOwner -JustPath -Path $_.FullName -Resolve if ($PSCmdlet.ParameterSetName -eq 'Principal') {} else { if ($IdentityOwner.OwnerSid -ne 'S-1-5-32-544') { Set-FileOwner -Path $Path -JustPath -Owner 'S-1-5-32-544' } else { Write-Verbose "Set-WinADShare - Owner of $($_.FullName) already set to $($IdentityOwner.OwnerName). Skipping." } } } else { Get-FilePermission -Path $_ -ResolveTypes -Extended } } } } } function Set-WinADTombstoneLifetime { [cmdletBinding()] param([alias('ForestName')][string] $Forest, [int] $Days = 180, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation $QueryServer = $ForestInformation.QueryServers['Forest']['HostName'][0] $Partition = $((Get-ADRootDSE -Server $QueryServer).configurationNamingContext) Set-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$Partition" -Partition $Partition -Replace @{tombstonelifetime = $Days } -Server $QueryServer } function Sync-DomainController { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $DistinguishedName = (Get-ADDomain -Server $QueryServer).DistinguishedName ($ForestInformation['DomainDomainControllers']["$Domain"]).Name | ForEach-Object { Write-Verbose -Message "Sync-DomainController - Forcing synchronization $_" repadmin /syncall $_ $DistinguishedName /e /A | Out-Null } } } function Test-ADDomainController { [CmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'DomainController', 'ComputerName')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [Parameter(Mandatory = $false)][PSCredential] $Credential = $null, [System.Collections.IDictionary] $ExtendedForestInformation) $CredentialParameter = @{} if ($null -ne $Credential) { $CredentialParameter['Credential'] = $Credential } $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation $Output = foreach ($Computer in $ForestInformation.ForestDomainControllers.HostName) { $Result = Invoke-Command -ComputerName $Computer -ScriptBlock { dcdiag.exe /v /c /Skip:OutboundSecureChannels } @CredentialParameter for ($Line = 0; $Line -lt $Result.length; $Line++) { if ($Result[$Line] -match '^\s{9}.{25} (\S+) (\S+) test$') { $Result[$Line] = $Result[$Line] + ' ' + $Result[$Line + 2].Trim() } if ($Result[$Line] -match '^\s{6}Starting test: \S+$') { $LineStart = $Line } if ($Result[$Line] -match '^\s{9}.{25} (\S+) (\S+) test (\S+)$') { $DiagnosticResult = [PSCustomObject] @{ComputerName = $Computer Target = $Matches[1] Test = $Matches[3] Result = $Matches[2] -eq 'passed' Data = $Result[$LineStart..$Line] -join [System.Environment]::NewLine } $DiagnosticResult } } } $Output } function Test-ADRolesAvailability { [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [System.Collections.IDictionary] $ExtendedForestInformation) $Roles = Get-WinADForestRoles -Forest $Forest -IncludeDomains $IncludeDomains -IncludeDomainControllers $IncludeDomainControllers -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation if ($IncludeDomains) { [PSCustomObject] @{PDCEmulator = $Roles['PDCEmulator'] PDCEmulatorAvailability = if ($Roles['PDCEmulator']) { (Test-NetConnection -ComputerName $Roles['PDCEmulator']).PingSucceeded } else { $false } RIDMaster = $Roles['RIDMaster'] RIDMasterAvailability = if ($Roles['RIDMaster']) { (Test-NetConnection -ComputerName $Roles['RIDMaster']).PingSucceeded } else { $false } InfrastructureMaster = $Roles['InfrastructureMaster'] InfrastructureMasterAvailability = if ($Roles['InfrastructureMaster']) { (Test-NetConnection -ComputerName $Roles['InfrastructureMaster']).PingSucceeded } else { $false } } } else { [PSCustomObject] @{SchemaMaster = $Roles['SchemaMaster'] SchemaMasterAvailability = if ($Roles['SchemaMaster']) { (Test-NetConnection -ComputerName $Roles['SchemaMaster']).PingSucceeded } else { $false } DomainNamingMaster = $Roles['DomainNamingMaster'] DomainNamingMasterAvailability = if ($Roles['DomainNamingMaster']) { (Test-NetConnection -ComputerName $Roles['DomainNamingMaster']).PingSucceeded } else { $false } } } } function Test-ADSiteLinks { [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string] $Splitter, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation if (($ForestInformation.ForestDomainControllers).Count -eq 1) { [ordered] @{SiteLinksManual = 'No sitelinks, single DC' SiteLinksAutomatic = 'No sitelinks, single DC' SiteLinksUseNotify = 'No sitelinks, single DC' SiteLinksNotUsingNotify = 'No sitelinks, single DC' SiteLinksUseNotifyCount = 0 SiteLinksNotUsingNotifyCount = 0 SiteLinksManualCount = 0 SiteLinksAutomaticCount = 0 SiteLinksTotalCount = 0 Comment = 'No sitelinks, single DC' } } else { [Array] $SiteLinks = Get-WinADSiteConnections -ExtendedForestInformation $ForestInformation if ($SiteLinks) { $Collection = @($SiteLinks).Where( { $_.Options -notcontains 'IsGenerated' -and $_.EnabledConnection -eq $true }, 'Split') $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 Comment = 'OK' } } else { [ordered] @{SiteLinksManual = 'No sitelinks' SiteLinksAutomatic = 'No sitelinks' SiteLinksUseNotify = 'No sitelinks' SiteLinksNotUsingNotify = 'No sitelinks' SiteLinksUseNotifyCount = 0 SiteLinksNotUsingNotifyCount = 0 SiteLinksManualCount = 0 SiteLinksAutomaticCount = 0 SiteLinksTotalCount = 0 Comment = 'Error' } } } } function Test-DNSNameServers { [cmdletBinding()] param([string] $DomainController, [string] $Domain) if ($DomainController) { $AllDomainControllers = (Get-ADDomainController -Server $Domain -Filter { IsReadOnly -eq $false }).HostName try { $Hosts = Get-DnsServerResourceRecord -ZoneName $Domain -ComputerName $DomainController -RRType NS -ErrorAction Stop $NameServers = (($Hosts | Where-Object { $_.HostName -eq '@' }).RecordData.NameServer) -replace ".$" $Compare = ((Compare-Object -ReferenceObject $AllDomainControllers -DifferenceObject $NameServers -IncludeEqual).SideIndicator -notin @('=>', '<=')) [PSCustomObject] @{DomainControllers = $AllDomainControllers NameServers = $NameServers Status = $Compare Comment = "Name servers found $($NameServers -join ', ')" } } catch { [PSCustomObject] @{DomainControllers = $AllDomainControllers NameServers = $null Status = $false Comment = $_.Exception.Message } } } } function Test-FSMORolesAvailability { [cmdletBinding()] param([string] $Domain = $Env:USERDNSDOMAIN) $DC = Get-ADDomainController -Server $Domain -Filter * $Output = foreach ($S in $DC) { if ($S.OperationMasterRoles.Count -gt 0) { $Status = Test-Connection -ComputerName $S.HostName -Count 2 -Quiet } else { $Status = $null } foreach ($_ in $S.OperationMasterRoles) { [PSCustomObject] @{Role = $_ HostName = $S.HostName Status = $Status } } } $Output } Function Test-LDAP { [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 @('Add-ADACL', 'Get-ADACL', 'Get-ADACLOwner', 'Get-WinADBitlockerLapsSummary', 'Get-WinADDFSHealth', 'Get-WinADDiagnostics', 'Get-WinADDuplicateObject', 'Get-WinADForestObjectsConflict', 'Get-WinADForestOptionalFeatures', 'Get-WinADForestReplication', 'Get-WinADForestRoles', 'Get-WinADForestSchemaProperties', 'Get-WinADForestSites', 'Get-WinADGPOMissingPermissions', 'Get-WinADGPOSysvolFolders', 'Get-WinADGroupMember', 'Get-WinADLastBackup', 'Get-WinADLDAPBindingsSummary', 'Get-WinADLMSettings', 'Get-WinADObject', 'Get-WinADPrivilegedObjects', 'Get-WinADProxyAddresses', 'Get-WinADSharePermission', 'Get-WinADSiteConnections', 'Get-WinADSiteLinks', 'Get-WinADTomebstoneLifetime', 'Get-WinADTrusts', 'Get-WinADUserPrincipalName', 'Get-WinADUsersForeignSecurityPrincipalList', 'Get-WinADWellKnownFolders', 'Remove-ADACL', 'Remove-WinADDuplicateObject', 'Remove-WinADSharePermission', 'Rename-WinADUserPrincipalName', 'Repair-WinADEmailAddress', 'Set-ADACLOwner', 'Set-WinADDiagnostics', 'Set-WinADReplication', 'Set-WinADReplicationConnections', 'Set-WinADShare', 'Set-WinADTombstoneLifetime', 'Sync-DomainController', 'Test-ADDomainController', 'Test-ADRolesAvailability', 'Test-ADSiteLinks', 'Test-DNSNameServers', 'Test-FSMORolesAvailability', 'Test-LDAP') -Alias @('Get-WinADDomainRoles', 'Get-WinADForestTomebstoneLifetime', 'Get-WinADGPOSysvol', 'Get-WinADPriviligedObjects', 'Get-WinADRoles', 'Get-WinADUsersFP') # SIG # Begin signature block # MIIgQAYJKoZIhvcNAQcCoIIgMTCCIC0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUrQzdsXsKHb/pCyUakVPqJopn # +HOgghtvMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B # AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk # IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg # Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg # +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT # XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5 # a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g # 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1 # roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf # GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G # A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL # gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3 # cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr # EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+ # fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q # Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu # 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw # 8jCCBTAwggQYoAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQELBQAw # ZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ # d3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBS # b290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkGA1UE # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj # ZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUg # U2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjTsxx/ # DhGvZ3cH0wsxSRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3Gde2 # qvCchqXYJawOeSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrsk # acLCUvIUZ4qJRdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCiq85/ # 6XzLkqHlOzEcz+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xnTrXE # 94zRICUj6whkPlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8 # np+mM6n9Gd8lk9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD # VR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0w # azAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUF # BzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk # SURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdp # Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRw # Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3Js # ME8GA1UdIARIMEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczov # L3d3dy5kaWdpY2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7 # KgqjpepxA8Bg+S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823I # DzANBgkqhkiG9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh # 134LYP3DPQ/Er4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63X # X0R58zYUBor3nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwukaPA # JRHinBRHoXpoaK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC # /i9yfhzXSUWW6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG # /AeB+ova+YJJ92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBT0wggQloAMC # AQICEATV3B9I6snYUgC6zZqbKqcwDQYJKoZIhvcNAQELBQAwcjELMAkGA1UEBhMC # VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0 # LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2ln # bmluZyBDQTAeFw0yMDA2MjYwMDAwMDBaFw0yMzA3MDcxMjAwMDBaMHoxCzAJBgNV # BAYTAlBMMRIwEAYDVQQIDAnFmmzEhXNraWUxETAPBgNVBAcTCEthdG93aWNlMSEw # HwYDVQQKDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMxITAfBgNVBAMMGFByemVt # eXPFgmF3IEvFgnlzIEVWT1RFQzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC # ggEBAL+ygd4sga4ZC1G2xXvasYSijwWKgwapZ69wLaWaZZIlY6YvXTGQnIUnk+Tg # 7EoT7mQiMSaeSPOrn/Im6N74tkvRfQJXxY1cnt3U8//U5grhh/CULdd6M3/Z4h3n # MCq7LQ1YVaa4MYub9F8WOdXO84DANoNVG/t7YotL4vzqZil3S9pHjaidp3kOXGJc # vxrCPAkRFBKvUmYo23QPFa0Rd0qA3bFhn97WWczup1p90y2CkOf28OVOOObv1fNE # EqMpLMx0Yr04/h+LPAAYn6K4YtIu+m3gOhGuNc3B+MybgKePAeFIY4EQzbqvCMy1 # iuHZb6q6ggRyqrJ6xegZga7/gV0CAwEAAaOCAcUwggHBMB8GA1UdIwQYMBaAFFrE # uXsqCqOl6nEDwGD5LfZldQ5YMB0GA1UdDgQWBBQYsTUn6BxQICZOCZA0CxS0TZSU # ZjAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAw # bjA1oDOgMYYvaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1j # cy1nMS5jcmwwNaAzoDGGL2h0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFz # c3VyZWQtY3MtZzEuY3JsMEwGA1UdIARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYB # BQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGE # BggrBgEFBQcBAQR4MHYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0 # LmNvbTBOBggrBgEFBQcwAoZCaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp # Z2lDZXJ0U0hBMkFzc3VyZWRJRENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQC # MAAwDQYJKoZIhvcNAQELBQADggEBAJq9bM+JbCwEYuMBtXoNAfH1SRaMLXnLe0py # VK6el0Z1BtPxiNcF4iyHqMNVD4iOrgzLEVzx1Bf/sYycPEnyG8Gr2tnl7u1KGSjY # enX4LIXCZqNEDQCeTyMstNv931421ERByDa0wrz1Wz5lepMeCqXeyiawqOxA9fB/ # 106liR12vL2tzGC62yXrV6WhD6W+s5PpfEY/chuIwVUYXp1AVFI9wi2lg0gaTgP/ # rMfP1wfVvaKWH2Bm/tU5mwpIVIO0wd4A+qOhEia3vn3J2Zz1QDxEprLcLE9e3Gmd # G5+8xEypTR23NavhJvZMgY2kEXBEKEEDaXs0LoPbn6hMcepR2A4wggZqMIIFUqAD # AgECAhADAZoCOv9YsWvW1ermF/BmMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYT # AlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2Vy # dC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMTAeFw0xNDEw # MjIwMDAwMDBaFw0yNDEwMjIwMDAwMDBaMEcxCzAJBgNVBAYTAlVTMREwDwYDVQQK # EwhEaWdpQ2VydDElMCMGA1UEAxMcRGlnaUNlcnQgVGltZXN0YW1wIFJlc3BvbmRl # cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNkXfx8s+CCNeDg9sYq # 5kl1O8xu4FOpnx9kWeZ8a39rjJ1V+JLjntVaY1sCSVDZg85vZu7dy4XpX6X51Id0 # iEQ7Gcnl9ZGfxhQ5rCTqqEsskYnMXij0ZLZQt/USs3OWCmejvmGfrvP9Enh1DqZb # FP1FI46GRFV9GIYFjFWHeUhG98oOjafeTl/iqLYtWQJhiGFyGGi5uHzu5uc0LzF3 # gTAfuzYBje8n4/ea8EwxZI3j6/oZh6h+z+yMDDZbesF6uHjHyQYuRhDIjegEYNu8 # c3T6Ttj+qkDxss5wRoPp2kChWTrZFQlXmVYwk/PJYczQCMxr7GJCkawCwO+k8IkR # j3cCAwEAAaOCAzUwggMxMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYG # A1UdJQEB/wQMMAoGCCsGAQUFBwMIMIIBvwYDVR0gBIIBtjCCAbIwggGhBglghkgB # hv1sBwEwggGSMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20v # Q1BTMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAA # dABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQA # dQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQA # aQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIA # ZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcA # aABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQA # IABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4A # IABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMAsGCWCGSAGG/WwDFTAfBgNVHSME # GDAWgBQVABIrE5iymQftHt+ivlcNK2cCzTAdBgNVHQ4EFgQUYVpNJLZJMp1KKnka # g0v0HonByn0wfQYDVR0fBHYwdDA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwOKA2oDSGMmh0dHA6Ly9jcmw0 # LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3JsMHcGCCsGAQUF # BwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEG # CCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRB # c3N1cmVkSURDQS0xLmNydDANBgkqhkiG9w0BAQUFAAOCAQEAnSV+GzNNsiaBXJuG # ziMgD4CH5Yj//7HUaiwx7ToXGXEXzakbvFoWOQCd42yE5FpA+94GAYw3+puxnSR+ # /iCkV61bt5qwYCbqaVchXTQvH3Gwg5QZBWs1kBCge5fH9j/n4hFBpr1i2fAnPTgd # KG86Ugnw7HBi02JLsOBzppLA044x2C/jbRcTBu7kA7YUq/OPQ6dxnSHdFMoVXZJB # 2vkPgdGZdA0mxA5/G7X1oPHGdwYoFenYk+VVFvC7Cqsc21xIJ2bIo4sKHOWV2q7E # LlmgYd3a822iYemKC23sEhi991VUQAOSK2vCUcIKSK+w1G7g9BQKOhvjjz3Kr2qN # e9zYRDCCBs0wggW1oAMCAQICEAb9+QOWA63qAArrPye7uhswDQYJKoZIhvcNAQEF # BQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE # CxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJ # RCBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAwMDAwMFowYjELMAkG # A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp # Z2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMIIB # IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IItmfnKwkKVpYBzQHDSnlZU # XKnE0kEGj8kz/E1FkVyBn+0snPgWWd+etSQVwpi5tHdJ3InECtqvy15r7a2wcTHr # zzpADEZNk+yLejYIA6sMNP4YSYL+x8cxSIB8HqIPkg5QycaH6zY/2DDD/6b3+6LN # b3Mj/qxWBZDwMiEWicZwiPkFl32jx0PdAug7Pe2xQaPtP77blUjE7h6z8rwMK5nQ # xl0SQoHhg26Ccz8mSxSQrllmCsSNvtLOBq6thG9IhJtPQLnxTPKvmPv2zkBdXPao # 8S+v7Iki8msYZbHBc63X8djPHgp0XEK4aH631XcKJ1Z8D2KkPzIUYJX9BwSiCQID # AQABo4IDejCCA3YwDgYDVR0PAQH/BAQDAgGGMDsGA1UdJQQ0MDIGCCsGAQUFBwMB # BggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCDCCAdIGA1Ud # IASCAckwggHFMIIBtAYKYIZIAYb9bAABBDCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6 # Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggr # BgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAA # QwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAA # YQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUA # cgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4A # ZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAA # bABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAA # aQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIA # ZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMBIGA1UdEwEB/wQIMAYBAf8C # AQAweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaG # NGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RD # QS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFz # c3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0OBBYEFBUAEisTmLKZB+0e36K+Vw0rZwLN # MB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUA # A4IBAQBGUD7Jtygkpzgdtlspr1LPUukxR6tWXHvVDQtBs+/sdR90OPKyXGGinJXD # UOSCuSPRujqGcq04eKx1XRcXNHJHhZRW0eu7NoR3zCSl8wQZVann4+erYs37iy2Q # wsDStZS9Xk+xBdIOPRqpFFumhjFiqKgz5Js5p8T1zh14dpQlc+Qqq8+cdkvtX8JL # FuRLcEwAiR78xXm8TBJX/l/hHrwCXaj++wc4Tw3GXZG5D2dFzdaD7eeSDY2xaYxP # +1ngIw/Sqq4AfO6cQg7PkdcntxbuD8O9fAqg7iwIVYUiuOsYGk38KiGtSTGDR5V3 # cdyxG0tLHBCcdxTBnU8vWpUIKRAmMYIEOzCCBDcCAQEwgYYwcjELMAkGA1UEBhMC # VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0 # LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2ln # bmluZyBDQQIQBNXcH0jqydhSALrNmpsqpzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGC # NwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUDJ6Ux4R1 # 7XcE/WfHJJxuPOm6k40wDQYJKoZIhvcNAQEBBQAEggEAWIAvilt6HRqlIs8E92di # 1rnwrM08TMp+gPflbP8b/UWlzaYId8HSoZuy46E0L0r6pkgG+mvJIjdku3aqwqyx # re3oE3LyF2w7MpaNVF0ExJaDMND4Ivp8pE4m8gB0knGdRJ+jgidZY15oXkkuc+M2 # SRAZZhuGYxqhfptqV9DkI+S5fdVGJRROosFOu6SukA1zIhb1hqh7MroXi970Qt0E # oWMMa+Rdv80UkTZ91TENCZd2Nx36uyx/88uX5uon/OW2TUhztBU9w0Ts0+APMakK # Z4CANs3K43DqWOxmLS58s/ab9xLtvhCVhrJ17xinC2zUczyYvBa1LGQUovMoD5NO # AaGCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT # MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j # b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr # 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc # BgkqhkiG9w0BCQUxDxcNMjAwODI4MjAwMzMxWjAjBgkqhkiG9w0BCQQxFgQUoox9 # 6N6DtfiaR6SfimaJ4888C2MwDQYJKoZIhvcNAQEBBQAEggEAaffUWyA5ShgJTCyo # +cChQNqJpCsFTxacn4/v2aeUkh1qRdN5Hre9Z6+idBkxlJS2Z3WXw+PGnHSgYeEx # BPKpXfM+DH5QbhwFYHV5h19peyTNWf+TQEKDb9PoCYmCLnHyE/PrKDQc429v1MQQ # BKBNd6qigCqlqRRe+H/tyS/oWChqFX000Ccn3RZ/zhIlZDBxS3bhS0fNDjbhTdwo # 2523Ll5aL+CEPhzTB4Bl9Iks/jAEkPp3B+rA0v5jkGH+6RL5Ts89ACRHrTInPGQ9 # HceburhZWms9o1O1V63TMdVH8IDIycrZLs4HgibNeKlXqqxMV18oPYYtvkYYyz+X # YJJoRA== # SIG # End signature block |