ADEssentials.psm1
function Convert-DomainFqdnToNetBIOS { <# .SYNOPSIS Converts FQDN to NetBIOS name for Active Directory Domain .DESCRIPTION Converts FQDN to NetBIOS name for Active Directory Domain .PARAMETER DomainName DomainName for current forest or trusted forest .EXAMPLE Convert-DomainFqdnToNetBIOS -Domain 'ad.evotec.xyz' .EXAMPLE Convert-DomainFqdnToNetBIOS -Domain 'ad.evotec.pl' .NOTES General notes #> [cmdletBinding()] param ([string] $DomainName) if (-not $Script:CacheFQDN) { $Script:CacheFQDN = @{} } if ($Script:CacheFQDN[$DomainName]) { $Script:CacheFQDN[$DomainName] } else { $objRootDSE = [System.DirectoryServices.DirectoryEntry] "LDAP://$DomainName/RootDSE" $ConfigurationNC = $objRootDSE.configurationNamingContext $Searcher = [System.DirectoryServices.DirectorySearcher] @{SearchScope = "subtree" SearchRoot = "LDAP://cn=Partitions,$ConfigurationNC" Filter = "(&(objectcategory=Crossref)(dnsRoot=$DomainName)(netbiosname=*))" } $null = $Searcher.PropertiesToLoad.Add("netbiosname") $Script:CacheFQDN[$DomainName] = ($Searcher.FindOne()).Properties.Item("netbiosname") $Script:CacheFQDN[$DomainName] } } function Convert-ExchangeRecipient { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER RecipientTypeDetails Parameter description .PARAMETER RecipientType Parameter description .PARAMETER RemoteRecipientType Parameter description .EXAMPLE $Users = Get-ADUser -Filter * -Properties Mail, ProxyAddresses, msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails, MailNickName $UsersModified = foreach ($User in $Users) { [PSCUstomObject] @{ Name = $User.Name Mail = $User.Mail MailNickName = $User.MailNickName msExchRemoteRecipientType = Convert-ExchangeRecipient -msExchRemoteRecipientType $User.msExchRemoteRecipientType msExchRecipientDisplayType = Convert-ExchangeRecipient -msExchRecipientDisplayType $User.msExchRecipientDisplayType msExchRecipientTypeDetails = Convert-ExchangeRecipient -msExchRecipientTypeDetails $User.msExchRecipientTypeDetails ProxyAddresses = Convert-ExchangeEmail -AddSeparator -RemovePrefix -RemoveDuplicates -Separator ',' -Emails $User.ProxyAddresses } } $UsersModified | Out-HtmlView -Filtering -ScrollX .NOTES General notes #> [cmdletbinding(DefaultParameterSetName = 'msExchRecipientTypeDetails')] param([parameter(ParameterSetName = 'msExchRecipientTypeDetails')][alias('msExchRecipientTypeDetails')][string] $RecipientTypeDetails, [parameter(ParameterSetName = 'msExchRecipientDisplayType')][alias('msExchRecipientDisplayType')][string] $RecipientType, [parameter(ParameterSetName = 'msExchRemoteRecipientType')][alias('msExchRemoteRecipientType')][string] $RemoteRecipientType) if ($RecipientTypeDetails) { $msExchRecipientTypeDetails = [ordered] @{'1' = 'UserMailbox' '2' = 'LinkedMailbox' '4' = 'SharedMailbox' '16' = 'RoomMailbox' '32' = 'EquipmentMailbox' '128' = 'MailUser' '2147483648' = 'RemoteUserMailbox' '8589934592' = 'RemoteRoomMailbox' '17179869184' = 'RemoteEquipmentMailbox' '34359738368' = 'RemoteSharedMailbox' } $msExchRecipientTypeDetails[$RecipientTypeDetails] } elseif ($RecipientType) { $msExchRecipientDisplayType = [ordered] @{'-2147483642' = 'MailUser (RemoteUserMailbox)' '-2147481850' = 'MailUser (RemoteRoomMailbox)' '-2147481594' = 'MailUser (RemoteEquipmentMailbox)' '0' = 'UserMailbox (shared)' '1' = 'MailUniversalDistributionGroup' '6' = 'MailContact' '7' = 'UserMailbox (room)' '8' = 'UserMailbox (equipment)' '1073741824' = 'UserMailbox' '1073741833' = 'MailUniversalSecurityGroup' } $msExchRecipientDisplayType[$RecipientType] } elseif ($RemoteRecipientType) { $msExchRemoteRecipientType = [ordered] @{'1' = 'ProvisionMailbox' '2' = 'ProvisionArchive (On-Prem Mailbox)' '3' = 'ProvisionMailbox, ProvisionArchive' '4' = 'Migrated (UserMailbox)' '6' = 'ProvisionArchive, Migrated' '8' = 'DeprovisionMailbox' '10' = 'ProvisionArchive, DeprovisionMailbox' '16' = 'DeprovisionArchive (On-Prem Mailbox)' '17' = 'ProvisionMailbox, DeprovisionArchive' '20' = 'Migrated, DeprovisionArchive' '24' = 'DeprovisionMailbox, DeprovisionArchive' '33' = 'ProvisionMailbox, RoomMailbox' '35' = 'ProvisionMailbox, ProvisionArchive, RoomMailbox' '36' = 'Migrated, RoomMailbox' '38' = 'ProvisionArchive, Migrated, RoomMailbox' '49' = 'ProvisionMailbox, DeprovisionArchive, RoomMailbox' '52' = 'Migrated, DeprovisionArchive, RoomMailbox' '65' = 'ProvisionMailbox, EquipmentMailbox' '67' = 'ProvisionMailbox, ProvisionArchive, EquipmentMailbox' '68' = 'Migrated, EquipmentMailbox' '70' = 'ProvisionArchive, Migrated, EquipmentMailbox' '81' = 'ProvisionMailbox, DeprovisionArchive, EquipmentMailbox' '84' = 'Migrated, DeprovisionArchive, EquipmentMailbox' '100' = 'Migrated, SharedMailbox' '102' = 'ProvisionArchive, Migrated, SharedMailbox' '116' = 'Migrated, DeprovisionArchive, SharedMailbox' } $msExchRemoteRecipientType[$RemoteRecipientType] } } 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-NetbiosName { [cmdletBinding()] param([Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [string[]] $Identity) process { foreach ($Ident in $Identity) { if ($Ident -like '*\*') { $NetbiosWithObject = $Ident -split "\\" if ($NetbiosWithObject.Count -eq 2) { $LDAPQuery = ([ADSI]"LDAP://$($NetbiosWithObject[0])") $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $LDAPQuery.distinguishedName -ToDomainCN [PSCustomObject] @{DomainName = $DomainName Name = $NetbiosWithObject[1] } } else { [PSCustomObject] @{DomainName = '' Name = $Ident } } } else { [PSCustomObject] @{DomainName = '' Name = $Ident } } } } } function ConvertFrom-SID { <# .SYNOPSIS Small command that can resolve SID values .DESCRIPTION Small command that can resolve SID values .PARAMETER SID Value to resolve .PARAMETER OnlyWellKnown Only resolve SID when it's well know SID. Otherwise return $null .PARAMETER OnlyWellKnownAdministrative Only resolve SID when it's administrative well know SID. Otherwise return $null .PARAMETER DoNotResolve Uses only dicrionary values without querying AD .EXAMPLE ConvertFrom-SID -SID 'S-1-5-8', 'S-1-5-9', 'S-1-5-11', 'S-1-5-18', 'S-1-1-0' -DoNotResolve .NOTES General notes #> [cmdletbinding(DefaultParameterSetName = 'Standard')] param([Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'OnlyWellKnown')] [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')] [string[]] $SID, [Parameter(ParameterSetName = 'OnlyWellKnown')][switch] $OnlyWellKnown, [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')][switch] $OnlyWellKnownAdministrative, [Parameter(ParameterSetName = 'Standard')][switch] $DoNotResolve) $WellKnownAdministrative = @{'S-1-5-18' = [PSCustomObject] @{Name = 'NT AUTHORITY\SYSTEM' SID = 'S-1-5-18' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-5-32-544' = [PSCustomObject] @{Name = 'BUILTIN\Administrators' SID = 'S-1-5-32-544' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } } $wellKnownSIDs = @{'S-1-0' = [PSCustomObject] @{Name = 'Null AUTHORITY' SID = 'S-1-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-0-0' = [PSCustomObject] @{Name = 'NULL SID' SID = 'S-1-0-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-1' = [PSCustomObject] @{Name = 'WORLD AUTHORITY' SID = 'S-1-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-1-0' = [PSCustomObject] @{Name = 'Everyone' SID = 'S-1-1-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-2' = [PSCustomObject] @{Name = 'LOCAL AUTHORITY' SID = 'S-1-2' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-2-0' = [PSCustomObject] @{Name = 'LOCAL' SID = 'S-1-2-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-2-1' = [PSCustomObject] @{Name = 'CONSOLE LOGON' SID = 'S-1-2-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3' = [PSCustomObject] @{Name = 'CREATOR AUTHORITY' SID = 'S-1-3' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-0' = [PSCustomObject] @{Name = 'CREATOR OWNER' SID = 'S-1-3-0' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-3-1' = [PSCustomObject] @{Name = 'CREATOR GROUP' SID = 'S-1-3-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-2' = [PSCustomObject] @{Name = 'CREATOR OWNER SERVER' SID = 'S-1-3-2' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-3' = [PSCustomObject] @{Name = 'CREATOR GROUP SERVER' SID = 'S-1-3-3' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-4' = [PSCustomObject] @{Name = 'OWNER RIGHTS' SID = 'S-1-3-4' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-0' = [PSCustomObject] @{Name = 'NT SERVICE\ALL SERVICES' SID = 'S-1-5-80-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-4' = [PSCustomObject] @{Name = 'Non-unique Authority' SID = 'S-1-4' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5' = [PSCustomObject] @{Name = 'NT AUTHORITY' SID = 'S-1-5' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-1' = [PSCustomObject] @{Name = 'NT AUTHORITY\DIALUP' SID = 'S-1-5-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-2' = [PSCustomObject] @{Name = 'NT AUTHORITY\NETWORK' SID = 'S-1-5-2' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-3' = [PSCustomObject] @{Name = 'NT AUTHORITY\BATCH' SID = 'S-1-5-3' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-4' = [PSCustomObject] @{Name = 'NT AUTHORITY\INTERACTIVE' SID = 'S-1-5-4' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-6' = [PSCustomObject] @{Name = 'NT AUTHORITY\SERVICE' SID = 'S-1-5-6' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-7' = [PSCustomObject] @{Name = 'NT AUTHORITY\ANONYMOUS LOGON' SID = 'S-1-5-7' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-8' = [PSCustomObject] @{Name = 'NT AUTHORITY\PROXY' SID = 'S-1-5-8' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-9' = [PSCustomObject] @{Name = 'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS' SID = 'S-1-5-9' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-10' = [PSCustomObject] @{Name = 'NT AUTHORITY\SELF' SID = 'S-1-5-10' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-11' = [PSCustomObject] @{Name = 'NT AUTHORITY\Authenticated Users' SID = 'S-1-5-11' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-12' = [PSCustomObject] @{Name = 'NT AUTHORITY\RESTRICTED' SID = 'S-1-5-12' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-13' = [PSCustomObject] @{Name = 'NT AUTHORITY\TERMINAL SERVER USER' SID = 'S-1-5-13' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-14' = [PSCustomObject] @{Name = 'NT AUTHORITY\REMOTE INTERACTIVE LOGON' SID = 'S-1-5-14' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-15' = [PSCustomObject] @{Name = 'NT AUTHORITY\This Organization' SID = 'S-1-5-15' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-17' = [PSCustomObject] @{Name = 'NT AUTHORITY\IUSR' SID = 'S-1-5-17' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-18' = [PSCustomObject] @{Name = 'NT AUTHORITY\SYSTEM' SID = 'S-1-5-18' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-5-19' = [PSCustomObject] @{Name = 'NT AUTHORITY\NETWORK SERVICE' SID = 'S-1-5-19' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-20' = [PSCustomObject] @{Name = 'NT AUTHORITY\NETWORK SERVICE' SID = 'S-1-5-20' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-544' = [PSCustomObject] @{Name = 'BUILTIN\Administrators' SID = 'S-1-5-32-544' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-5-32-545' = [PSCustomObject] @{Name = 'BUILTIN\Users' SID = 'S-1-5-32-545' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-546' = [PSCustomObject] @{Name = 'BUILTIN\Guests' SID = 'S-1-5-32-546' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-547' = [PSCustomObject] @{Name = 'BUILTIN\Power Users' SID = 'S-1-5-32-547' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-548' = [PSCustomObject] @{Name = 'BUILTIN\Account Operators' SID = 'S-1-5-32-548' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-549' = [PSCustomObject] @{Name = 'BUILTIN\Server Operators' SID = 'S-1-5-32-549' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-550' = [PSCustomObject] @{Name = 'BUILTIN\Print Operators' SID = 'S-1-5-32-550' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-551' = [PSCustomObject] @{Name = 'BUILTIN\Backup Operators' SID = 'S-1-5-32-551' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-552' = [PSCustomObject] @{Name = 'BUILTIN\Replicators' SID = 'S-1-5-32-552' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-64-10' = [PSCustomObject] @{Name = 'NT AUTHORITY\NTLM Authentication' SID = 'S-1-5-64-10' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-64-14' = [PSCustomObject] @{Name = 'NT AUTHORITY\SChannel Authentication' SID = 'S-1-5-64-14' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-64-21' = [PSCustomObject] @{Name = 'NT AUTHORITY\Digest Authentication' SID = 'S-1-5-64-21' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80' = [PSCustomObject] @{Name = 'NT SERVICE' SID = 'S-1-5-80' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-83-0' = [PSCustomObject] @{Name = 'NT VIRTUAL MACHINE\Virtual Machines' SID = 'S-1-5-83-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-0' = [PSCustomObject] @{Name = 'Untrusted Mandatory Level' SID = 'S-1-16-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-4096' = [PSCustomObject] @{Name = 'Low Mandatory Level' SID = 'S-1-16-4096' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-8192' = [PSCustomObject] @{Name = 'Medium Mandatory Level' SID = 'S-1-16-8192' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-8448' = [PSCustomObject] @{Name = 'Medium Plus Mandatory Level' SID = 'S-1-16-8448' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-12288' = [PSCustomObject] @{Name = 'High Mandatory Level' SID = 'S-1-16-12288' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-16384' = [PSCustomObject] @{Name = 'System Mandatory Level' SID = 'S-1-16-16384' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-20480' = [PSCustomObject] @{Name = 'Protected Process Mandatory Level' SID = 'S-1-16-20480' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-28672' = [PSCustomObject] @{Name = 'Secure Process Mandatory Level' SID = 'S-1-16-28672' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-554' = [PSCustomObject] @{Name = 'BUILTIN\Pre-Windows 2000 Compatible Access' SID = 'S-1-5-32-554' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-555' = [PSCustomObject] @{Name = 'BUILTIN\Remote Desktop Users' SID = 'S-1-5-32-555' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-556' = [PSCustomObject] @{Name = 'BUILTIN\Network Configuration Operators' SID = 'S-1-5-32-556' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-557' = [PSCustomObject] @{Name = 'BUILTIN\Incoming Forest Trust Builders' SID = 'S-1-5-32-557' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-558' = [PSCustomObject] @{Name = 'BUILTIN\Performance Monitor Users' SID = 'S-1-5-32-558' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-559' = [PSCustomObject] @{Name = 'BUILTIN\Performance Log Users' SID = 'S-1-5-32-559' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-560' = [PSCustomObject] @{Name = 'BUILTIN\Windows Authorization Access Group' SID = 'S-1-5-32-560' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-561' = [PSCustomObject] @{Name = 'BUILTIN\Terminal Server License Servers' SID = 'S-1-5-32-561' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-562' = [PSCustomObject] @{Name = 'BUILTIN\Distributed COM Users' SID = 'S-1-5-32-562' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-569' = [PSCustomObject] @{Name = 'BUILTIN\Cryptographic Operators' SID = 'S-1-5-32-569' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-573' = [PSCustomObject] @{Name = 'BUILTIN\Event Log Readers' SID = 'S-1-5-32-573' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-574' = [PSCustomObject] @{Name = 'BUILTIN\Certificate Service DCOM Access' SID = 'S-1-5-32-574' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-575' = [PSCustomObject] @{Name = 'BUILTIN\RDS Remote Access Servers' SID = 'S-1-5-32-575' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-576' = [PSCustomObject] @{Name = 'BUILTIN\RDS Endpoint Servers' SID = 'S-1-5-32-576' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-577' = [PSCustomObject] @{Name = 'BUILTIN\RDS Management Servers' SID = 'S-1-5-32-577' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-578' = [PSCustomObject] @{Name = 'BUILTIN\Hyper-V Administrators' SID = 'S-1-5-32-578' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-579' = [PSCustomObject] @{Name = 'BUILTIN\Access Control Assistance Operators' SID = 'S-1-5-32-579' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-580' = [PSCustomObject] @{Name = 'BUILTIN\Remote Management Users' SID = 'S-1-5-32-580' DomainName = '' Type = 'WellKnownGroup' Error = '' } } foreach ($S in $SID) { if ($OnlyWellKnownAdministrative) { if ($WellKnownAdministrative[$S]) { $WellKnownAdministrative[$S] } } elseif ($OnlyWellKnown) { if ($wellKnownSIDs[$S]) { $wellKnownSIDs[$S] } } else { if ($wellKnownSIDs[$S]) { $wellKnownSIDs[$S] } else { if ($DoNotResolve) { if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512") { [PSCustomObject] @{Name = $S SID = $S DomainName = '' Type = 'Administrative' Error = '' } } else { [PSCustomObject] @{Name = $S SID = $S DomainName = '' Error = '' Type = 'NotAdministrative' } } } else { try { if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512") { $Type = 'Administrative' } else { $Type = 'NotAdministrative' } $Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value [PSCustomObject] @{Name = $Name SID = $S DomainName = (ConvertFrom-NetbiosName -Identity $Name).DomainName Type = $Type Error = '' } } catch { [PSCustomObject] @{Name = $S SID = $S DomainName = '' Error = $_.Exception.Message -replace [environment]::NewLine, ' ' Type = 'Unknown' } } } } } } } function Convert-Identity { <# .SYNOPSIS Small command that tries to resolve any given object .DESCRIPTION Small command that tries to resolve any given object - be it SID, DN, FSP or Netbiosname .PARAMETER Identity Type to resolve in form of Identity, DN, SID .PARAMETER SID Allows to pass SID directly, rather then going thru verification process .PARAMETER Name Allows to pass Name directly, rather then going thru verification process .EXAMPLE $Identity = @( 'S-1-5-4' 'S-1-5-4' 'S-1-5-11' 'S-1-5-32-549' 'S-1-5-32-550' 'S-1-5-32-548' 'S-1-5-64-10' 'S-1-5-64-14' 'S-1-5-64-21' 'S-1-5-18' 'S-1-5-19' 'S-1-5-32-544' 'S-1-5-20-20-10-51' # Wrong SID 'S-1-5-21-853615985-2870445339-3163598659-512' 'S-1-5-21-3661168273-3802070955-2987026695-512' 'S-1-5-21-1928204107-2710010574-1926425344-512' 'CN=Test Test 2,OU=Users,OU=Production,DC=ad,DC=evotec,DC=pl' 'Test Local Group' 'przemyslaw.klys@evotec.pl' 'test2' 'NT AUTHORITY\NETWORK' 'NT AUTHORITY\SYSTEM' 'S-1-5-21-853615985-2870445339-3163598659-519' 'TEST\some' 'EVOTECPL\Domain Admins' 'NT AUTHORITY\INTERACTIVE' 'INTERACTIVE' 'EVOTEC\Domain Admins' 'EVOTECPL\Domain Admins' 'Test\Domain Admins' 'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # Valid 'CN=S-1-5-21-1928204107-2710010574-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # not valid 'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # cached ) $TestOutput = Convert-Identity -Identity $Identity -Verbose Output: Name SID DomainName Type Error ---- --- ---------- ---- ----- NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup NT AUTHORITY\Authenticated Users S-1-5-11 WellKnownGroup BUILTIN\Server Operators S-1-5-32-549 WellKnownGroup BUILTIN\Print Operators S-1-5-32-550 WellKnownGroup BUILTIN\Account Operators S-1-5-32-548 WellKnownGroup NT AUTHORITY\NTLM Authentication S-1-5-64-10 WellKnownGroup NT AUTHORITY\SChannel Authentication S-1-5-64-14 WellKnownGroup NT AUTHORITY\Digest Authentication S-1-5-64-21 WellKnownGroup NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative NT AUTHORITY\NETWORK SERVICE S-1-5-19 WellKnownGroup BUILTIN\Administrators S-1-5-32-544 WellKnownAdministrative S-1-5-20-20-10-51 S-1-5-20-20-10-51 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated." EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative EVOTECPL\TestingAD S-1-5-21-3661168273-3802070955-2987026695-1111 ad.evotec.pl NotAdministrative EVOTEC\Test Local Group S-1-5-21-853615985-2870445339-3163598659-3610 ad.evotec.xyz NotAdministrative EVOTEC\przemyslaw.klys S-1-5-21-853615985-2870445339-3163598659-1105 ad.evotec.xyz NotAdministrative test2 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated." NT AUTHORITY\NETWORK S-1-5-2 WellKnownGroup NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative EVOTEC\Enterprise Admins S-1-5-21-853615985-2870445339-3163598659-519 ad.evotec.xyz Administrative TEST\some S-1-5-21-1928204107-2710010574-1926425344-1106 test.evotec.pl NotAdministrative EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative S-1-5-21-1928204107-2710010574-512 S-1-5-21-1928204107-2710010574-512 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated." TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'Identity')] param([parameter(ParameterSetName = 'Identity', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)][string[]] $Identity, [parameter(ParameterSetName = 'SID', Mandatory)][System.Security.Principal.SecurityIdentifier[]] $SID, [parameter(ParameterSetName = 'Name', Mandatory)][string[]] $Name) Begin { if (-not $Script:GlobalCacheSidConvert) { $Script:GlobalCacheSidConvert = @{} } } Process { if ($Identity) { foreach ($Ident in $Identity) { $MatchRegex = [Regex]::Matches($Ident, "S-\d-\d+-(\d+-|){1,14}\d+") if ($Script:GlobalCacheSidConvert[$Ident]) { Write-Verbose "Convert-Identity - Processing $Ident (Cache)" $Script:GlobalCacheSidConvert[$Ident] } elseif ($MatchRegex.Success) { Write-Verbose "Convert-Identity - Processing $Ident (SID)" if ($MatchRegex.Value -ne $Ident) { $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $MatchRegex.Value } else { $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $Ident } $Script:GlobalCacheSidConvert[$Ident] } elseif ($Ident -like '*DC=*') { Write-Verbose "Convert-Identity - Processing $Ident (DistinguishedName)" try { $Object = [adsi]"LDAP://$($Ident)" $SIDValue = [System.Security.Principal.SecurityIdentifier]::new($Object.objectSid.Value, 0).Value $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue } catch { $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Ident SID = $null DomainName = '' Type = 'Unknown' Error = $_.Exception.Message -replace [environment]::NewLine, ' ' } } $Script:GlobalCacheSidConvert[$Ident] } else { Write-Verbose "Convert-Identity - Processing $Ident (Other)" try { $SIDValue = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue } catch { $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Ident SID = $null DomainName = '' Type = 'Unknown' Error = $_.Exception.Message -replace [environment]::NewLine, ' ' } } $Script:GlobalCacheSidConvert[$Ident] } } } else { if ($SID) { foreach ($S in $SID) { if ($Script:GlobalCacheSidConvert[$S]) { $Script:GlobalCacheSidConvert[$S] } else { $Script:GlobalCacheSidConvert[$S] = ConvertFrom-SID -SID $S $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-DistinguishedName { <# .SYNOPSIS Converts CanonicalName to DistinguishedName .DESCRIPTION Converts CanonicalName to DistinguishedName for 3 different options .PARAMETER CanonicalName One or multiple canonical names .PARAMETER ToOU Converts CanonicalName to OrganizationalUnit DistinguishedName .PARAMETER ToObject Converts CanonicalName to Full Object DistinguishedName .PARAMETER ToDomain Converts CanonicalName to Domain DistinguishedName .EXAMPLE $CanonicalObjects = @( 'ad.evotec.xyz/Production/Groups/Security/ITR03_AD Admins' 'ad.evotec.xyz/Production/Accounts/Special/SADM Testing 2' ) $CanonicalOU = @( 'ad.evotec.xyz/Production/Groups/Security/NetworkAdministration' 'ad.evotec.xyz/Production' ) $CanonicalDomain = @( 'ad.evotec.xyz/Production/Groups/Security/ITR03_AD Admins' 'ad.evotec.pl' 'ad.evotec.xyz' 'test.evotec.pl' 'ad.evotec.xyz/Production' ) $CanonicalObjects | ConvertTo-DistinguishedName -ToObject $CanonicalOU | ConvertTo-DistinguishedName -ToOU $CanonicalDomain | ConvertTo-DistinguishedName -ToDomain Output: CN=ITR03_AD Admins,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz CN=SADM Testing 2,OU=Special,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz Output2: OU=NetworkAdministration,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz OU=Production,DC=ad,DC=evotec,DC=xyz Output3: DC=ad,DC=evotec,DC=xyz DC=ad,DC=evotec,DC=pl DC=ad,DC=evotec,DC=xyz DC=test,DC=evotec,DC=pl DC=ad,DC=evotec,DC=xyz .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'ToDomain')] param([Parameter(ParameterSetName = 'ToOU')] [Parameter(ParameterSetName = 'ToObject')] [Parameter(ParameterSetName = 'ToDomain')] [alias('Identity', 'CN')][Parameter(ValueFromPipeline, Mandatory, ValueFromPipelineByPropertyName, Position = 0)][string[]] $CanonicalName, [Parameter(ParameterSetName = 'ToOU')][switch] $ToOU, [Parameter(ParameterSetName = 'ToObject')][switch] $ToObject, [Parameter(ParameterSetName = 'ToDomain')][switch] $ToDomain) Process { foreach ($CN in $CanonicalName) { if ($ToObject) { $ADObject = $CN.Replace(',', '\,').Split('/') [string]$DN = "CN=" + $ADObject[$ADObject.count - 1] for ($i = $ADObject.count - 2; $i -ge 1; $i--) { $DN += ",OU=" + $ADObject[$i] } $ADObject[0].split(".") | ForEach-Object { $DN += ",DC=" + $_ } } elseif ($ToOU) { $ADObject = $CN.Replace(',', '\,').Split('/') [string]$DN = "OU=" + $ADObject[$ADObject.count - 1] for ($i = $ADObject.count - 2; $i -ge 1; $i--) { $DN += ",OU=" + $ADObject[$i] } $ADObject[0].split(".") | ForEach-Object { $DN += ",DC=" + $_ } } else { $ADObject = $CN.Replace(',', '\,').Split('/') $DN = 'DC=' + $ADObject[0].Replace('.', ',DC=') } $DN } } } 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 (19043)' = 'Windows 10 21H1' '10.0 (19042)' = 'Windows 10 20H2' '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.19043' = 'Windows 10 21H1' '10.0.19042' = 'Windows 10 20H2' '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 Copy-Dictionary { [alias('Copy-Hashtable', 'Copy-OrderedHashtable')] [cmdletbinding()] param([System.Collections.IDictionary] $Dictionary) $ms = [System.IO.MemoryStream]::new() $bf = [System.Runtime.Serialization.Formatters.Binary.BinaryFormatter]::new() $bf.Serialize($ms, $Dictionary) $ms.Position = 0 $clone = $bf.Deserialize($ms) $ms.Close() $clone } 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-ADEncryptionTypes { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Value Parameter description .EXAMPLE Get-ADEncryptionTypes -Value 24 Output: AES128-CTS-HMAC-SHA1-96 AES256-CTS-HMAC-SHA1-96 .NOTES General notes #> [cmdletbinding()] Param([parameter(Mandatory = $false, ValueFromPipeline = $True)][int32]$Value) [String[]]$EncryptionTypes = @(Foreach ($V in $Value) { if ([int32]$V -band 0x00000001) { "DES-CBC-CRC" } if ([int32]$V -band 0x00000002) { "DES-CBC-MD5" } if ([int32]$V -band 0x00000004) { "RC4-HMAC" } if ([int32]$V -band 0x00000008) { "AES128-CTS-HMAC-SHA1-96" } if ([int32]$V -band 0x00000010) { "AES256-CTS-HMAC-SHA1-96" } if ([int32]$V -band 0x00000020) { "FAST-supported" } if ([int32]$V -band 0x00000040) { "Compound-identity-supported" } if ([int32]$V -band 0x00000080) { "Claims-supported" } if ([int32]$V -band 0x00000200) { "Resource-SID-compression-disabled" } }) $EncryptionTypes } 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 Only" } if ([int32]$V -band 0x00000004) { "Quarantined Domain" } if ([int32]$V -band 0x00000008) { "Forest Transitive" } if ([int32]$V -band 0x00000010) { "Cross Organization" } if ([int32]$V -band 0x00000020) { "Within Forest" } if ([int32]$V -band 0x00000040) { "Treat as External" } if ([int32]$V -band 0x00000080) { "Uses RC4 Encryption" } if ([int32]$V -band 0x00000200) { "No TGT DELEGATION" } if ([int32]$V -band 0x00000800) { "Enable TGT DELEGATION" } if ([int32]$V -band 0x00000400) { "PIM Trust" } }) return $TrustAttributes } function Get-CimData { <# .SYNOPSIS Helper function for retreiving CIM data from local and remote computers .DESCRIPTION Helper function for retreiving CIM data from local and remote computers .PARAMETER ComputerName Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name (FQDN), a NetBIOS name, or an IP address. If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component Object Model (COM). .PARAMETER Protocol Specifies the protocol to use. The acceptable values for this parameter are: DCOM, Default, or Wsman. .PARAMETER Class Specifies the name of the CIM class for which to retrieve the CIM instances. You can use tab completion to browse the list of classes, because PowerShell gets a list of classes from the local WMI server to provide a list of class names. .PARAMETER Properties Specifies a set of instance properties to retrieve. Use this parameter when you need to reduce the size of the object returned, either in memory or over the network. The object returned also contains the key properties even if you have not listed them using the Property parameter. Other properties of the class are present but they are not populated. .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([parameter(Mandatory)][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 -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties } else { $Option = New-CimSessionOption -Protocol $Protocol $Session = New-CimSession -ComputerName $Computers -SessionOption $Option -ErrorAction SilentlyContinue $Info = Get-CimInstance -ClassName $Class -CimSession $Session -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties $null = Remove-CimSession -CimSession $Session -ErrorAction SilentlyContinue $Info } } foreach ($E in $ErrorsToProcess) { Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)" } $Computers = $ComputersSplit[0] if ($Computers.Count -gt 0) { $Info = Get-CimInstance -ClassName $Class -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsLocal | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties $Info | Add-Member -Name 'PSComputerName' -Value $Computers -MemberType NoteProperty -Force $Info } foreach ($E in $ErrorsLocal) { Write-Warning -Message "Get-CimData - No data for computer $($Env:COMPUTERNAME). Failed with errror: $($E.Exception.Message)" }) $CimObject } function Get-FileName { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Extension Parameter description .PARAMETER Temporary Parameter description .PARAMETER TemporaryFileOnly Parameter description .EXAMPLE Get-FileName -Temporary Output: 3ymsxvav.tmp .EXAMPLE Get-FileName -Temporary Output: C:\Users\pklys\AppData\Local\Temp\tmpD74C.tmp .EXAMPLE Get-FileName -Temporary -Extension 'xlsx' Output: C:\Users\pklys\AppData\Local\Temp\tmp45B6.xlsx .NOTES General notes #> [CmdletBinding()] param([string] $Extension = 'tmp', [switch] $Temporary, [switch] $TemporaryFileOnly) if ($Temporary) { return "$($([System.IO.Path]::GetTempFileName()).Replace('.tmp','')).$Extension" } if ($TemporaryFileOnly) { return "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension" } } 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, [System.Security.AccessControl.FileSystemSecurity] $ACLS) 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) { if (-not $ACLS) { try { $ACLS = (Get-Acl -Path $FullPath -ErrorAction Stop) } catch { Write-Warning -Message "Get-FilePermission - Can't access $FullPath. Error $($_.Exception.Message)" continue } } $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-GitHubLatestRelease { [CmdLetBinding()] param([alias('ReleasesUrl')][uri] $Url) $ProgressPreference = 'SilentlyContinue' Try { [Array] $JsonOutput = (Invoke-WebRequest -Uri $Url -ErrorAction Stop | ConvertFrom-Json) foreach ($JsonContent in $JsonOutput) { [PSCustomObject] @{PublishDate = [DateTime] $JsonContent.published_at CreatedDate = [DateTime] $JsonContent.created_at PreRelease = [bool] $JsonContent.prerelease Version = [version] ($JsonContent.name -replace 'v', '') Tag = $JsonContent.tag_name Branch = $JsonContent.target_commitish Errors = '' } } } catch { [PSCustomObject] @{PublishDate = $null CreatedDate = $null PreRelease = $null Version = $null Tag = $null Branch = $null Errors = $_.Exception.Message } } $ProgressPreference = 'Continue' } 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, [System.StringComparison]::CurrentCultureIgnoreCase)) { $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($_, [System.StringComparison]::CurrentCultureIgnoreCase)) { $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-RandomStringName { [cmdletbinding()] param([int] $Size = 31, [switch] $ToLower, [switch] $ToUpper, [switch] $LettersOnly) [string] $MyValue = @(if ($LettersOnly) { ( -join ((1..$Size) | ForEach-Object { (65..90) + (97..122) | Get-Random } | ForEach-Object { [char]$_ })) } else { ( -join ((48..57) + (97..122) | Get-Random -Count $Size | ForEach-Object { [char]$_ })) }) if ($ToLower) { return $MyValue.ToLower() } if ($ToUpper) { return $MyValue.ToUpper() } return $MyValue } function Get-WinADForestControllers { [alias('Get-WinADDomainControllers')] <# .SYNOPSIS .DESCRIPTION Long description .PARAMETER TestAvailability Parameter description .EXAMPLE Get-WinADForestControllers -TestAvailability | Format-Table .EXAMPLE Get-WinADDomainControllers .EXAMPLE Get-WinADDomainControllers | Format-Table * Output: Domain HostName Forest IPV4Address IsGlobalCatalog IsReadOnly SchemaMaster DomainNamingMasterMaster PDCEmulator RIDMaster InfrastructureMaster Comment ------ -------- ------ ----------- --------------- ---------- ------------ ------------------------ ----------- --------- -------------------- ------- ad.evotec.xyz AD1.ad.evotec.xyz ad.evotec.xyz 192.168.240.189 True False True True True True True ad.evotec.xyz AD2.ad.evotec.xyz ad.evotec.xyz 192.168.240.192 True False False False False False False ad.evotec.pl ad.evotec.xyz False False False False False Unable to contact the server. This may be becau... .NOTES General notes #> [CmdletBinding()] param([string[]] $Domain, [switch] $TestAvailability, [switch] $SkipEmpty) try { $Forest = Get-ADForest if (-not $Domain) { $Domain = $Forest.Domains } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning "Get-WinADForestControllers - Couldn't use Get-ADForest feature. Error: $ErrorMessage" return } $Servers = foreach ($D in $Domain) { try { $LocalServer = Get-ADDomainController -Discover -DomainName $D -ErrorAction Stop -Writable $DC = Get-ADDomainController -Server $LocalServer.HostName[0] -Filter * -ErrorAction Stop foreach ($S in $DC) { $Server = [ordered] @{Domain = $D HostName = $S.HostName Name = $S.Name Forest = $Forest.RootDomain IPV4Address = $S.IPV4Address IPV6Address = $S.IPV6Address IsGlobalCatalog = $S.IsGlobalCatalog IsReadOnly = $S.IsReadOnly Site = $S.Site SchemaMaster = ($S.OperationMasterRoles -contains 'SchemaMaster') DomainNamingMaster = ($S.OperationMasterRoles -contains 'DomainNamingMaster') PDCEmulator = ($S.OperationMasterRoles -contains 'PDCEmulator') RIDMaster = ($S.OperationMasterRoles -contains 'RIDMaster') InfrastructureMaster = ($S.OperationMasterRoles -contains 'InfrastructureMaster') LdapPort = $S.LdapPort SslPort = $S.SslPort Pingable = $null Comment = '' } if ($TestAvailability) { $Server['Pingable'] = foreach ($_ in $Server.IPV4Address) { Test-Connection -Count 1 -Server $_ -Quiet -ErrorAction SilentlyContinue } } [PSCustomObject] $Server } } catch { [PSCustomObject]@{Domain = $D HostName = '' Name = '' Forest = $Forest.RootDomain IPV4Address = '' IPV6Address = '' IsGlobalCatalog = '' IsReadOnly = '' Site = '' SchemaMaster = $false DomainNamingMasterMaster = $false PDCEmulator = $false RIDMaster = $false InfrastructureMaster = $false LdapPort = '' SslPort = '' Pingable = $null Comment = $_.Exception.Message -replace "`n", " " -replace "`r", " " } } } if ($SkipEmpty) { return $Servers | Where-Object { $_.HostName -ne '' } } return $Servers } function 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 Remove-EmptyValue { [alias('Remove-EmptyValues')] [CmdletBinding()] param([alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable, [string[]] $ExcludeParameter, [switch] $Recursive, [int] $Rerun, [switch] $DoNotRemoveNull, [switch] $DoNotRemoveEmpty, [switch] $DoNotRemoveEmptyArray, [switch] $DoNotRemoveEmptyDictionary) foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } } if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } } } 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 = $_ try { $ACL = Get-Acl -Path $File -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)" } 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 -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" } } } } } else { Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -ErrorAction SilentlyContinue -ErrorVariable err | ForEach-Object -Process { $File = $_ try { $ACL = Get-Acl -Path $File.FullName -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)" } 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 -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" } } } } foreach ($e in $err) { Write-Warning "Set-FileOwner - Errors processing $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } } } } } 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($_, [System.StringComparison]::CurrentCultureIgnoreCase)) { $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 Start-TimeLog { [CmdletBinding()] param() [System.Diagnostics.Stopwatch]::StartNew() } function Stop-TimeLog { [CmdletBinding()] param ([Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time, [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner', [switch] $Continue) Begin {} Process { if ($Option -eq 'Array') { $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds" } else { $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" } } End { if (-not $Continue) { $Time.Stop() } return $TimeToExecute } } function Write-Color { <# .SYNOPSIS Write-Color is a wrapper around Write-Host. It provides: - Easy manipulation of colors, - Logging output to file (log) - Nice formatting options out of the box. .DESCRIPTION Author: przemyslaw.klys at evotec.pl Project website: https://evotec.xyz/hub/scripts/write-color-ps1/ Project support: https://github.com/EvotecIT/PSWriteColor Original idea: Josh (https://stackoverflow.com/users/81769/josh) .EXAMPLE Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow .EXAMPLE Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan .EXAMPLE Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1 .EXAMPLE Write-Color "1. ", "Option 1" -Color Yellow, Green Write-Color "2. ", "Option 2" -Color Yellow, Green Write-Color "3. ", "Option 3" -Color Yellow, Green Write-Color "4. ", "Option 4" -Color Yellow, Green Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1 .EXAMPLE Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." ` -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss" Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." ` -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" .EXAMPLE # Added in 0.5 Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow wc -t "my text" -c yellow -b green wc -text "my text" -c red .NOTES Additional Notes: - TimeFormat https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx #> [alias('Write-Colour')] [CmdletBinding()] param ([alias ('T')] [String[]]$Text, [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White, [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null, [alias ('Indent')][int] $StartTab = 0, [int] $LinesBefore = 0, [int] $LinesAfter = 0, [int] $StartSpaces = 0, [alias ('L')] [string] $LogFile = '', [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss', [alias ('LogTimeStamp')][bool] $LogTime = $true, [int] $LogRetry = 2, [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode', [switch] $ShowTime, [switch] $NoNewLine) $DefaultColor = $Color[0] if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) { Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated." return } if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host -Object "`n" -NoNewline } } if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewline } } if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewline } } if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline } if ($Text.Count -ne 0) { if ($Color.Count -ge $Text.Count) { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } } else { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } } } else { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline } } else { for ($i = 0; $i -lt $Color.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline } } } } if ($NoNewLine -eq $true) { Write-Host -NoNewline } else { Write-Host } if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } } if ($Text.Count -and $LogFile) { $TextToFile = "" for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] } $Saved = $false $Retry = 0 Do { $Retry++ try { if ($LogTime) { "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } else { "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } $Saved = $true } catch { if ($Saved -eq $false -and $Retry -eq $LogRetry) { $PSCmdlet.WriteError($_) } else { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)" } } } Until ($Saved -eq $true -or $Retry -ge $LogRetry) } } 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 ($Computer in $ComputerName) { if ($Computer -eq '' -or $null -eq $Computer) { $Computer = $Env:COMPUTERNAME } if ($Computer -ne $Env:COMPUTERNAME -and $Computer -ne $LocalComputerDNSName) { $Computer } else { $ComputersLocal = $Computer } } , @($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 } $Script:ConfigurationBitLocker = [ordered] @{Name = 'Bitlocker Summary' Enabled = $true Execute = { Get-WinADBitlockerLapsSummary -BitlockerOnly } Processing = {} Summary = {} Variables = @{} Solution = { if ($Script:Reporting['BitLocker']['Data']) { New-HTMLChart { New-ChartLegend -LegendPosition bottom -HorizontalAlign center -Color Red, Blue, Yellow New-ChartTheme -Palette palette5 foreach ($Object in $DataTable) { New-ChartRadial -Name $Object.Name -Value $Object.Money } } New-HTMLTable -DataTable $Script:Reporting['BitLocker']['Data'] -Filtering -SearchBuilder { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor BlizzardBlue New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 60 -BackgroundColor Alizarin -HighlightHeaders LastLogonDays, LastLogonDate -FailBackgroundColor LimeGreen New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator ge -Value 0 -BackgroundColor LimeGreen -HighlightHeaders PasswordLastSet, PasswordLastChangedDays New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 300 -BackgroundColor Orange -HighlightHeaders PasswordLastSet, PasswordLastChangedDays New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 360 -BackgroundColor Alizarin -HighlightHeaders PasswordLastSet, PasswordLastChangedDays } } } } $Script:ConfigurationLAPS = [ordered] @{Name = 'LAPS Summary' Enabled = $true Execute = { Get-WinADBitlockerLapsSummary -LapsOnly } Processing = {} Summary = {} Variables = @{} Solution = { if ($Script:Reporting['LAPS']['Data']) { New-HTMLChart { New-ChartLegend -LegendPosition bottom -HorizontalAlign center -Color Red, Blue, Yellow New-ChartTheme -Palette palette5 foreach ($Object in $DataTable) { New-ChartRadial -Name $Object.Name -Value $Object.Money } New-ChartEvent -DataTableID 'NewIDtoSearchInChart' -ColumnID 0 } New-HTMLTable -DataTable $Script:Reporting['LAPS']['Data'] -Filtering { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor BlizzardBlue New-HTMLTableCondition -Name 'LapsExpirationDays' -ComparisonType number -Operator lt -Value 0 -BackgroundColor BurntOrange -HighlightHeaders LapsExpirationDays, LapsExpirationTime -FailBackgroundColor LimeGreen New-HTMLTableCondition -Name 'Laps' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor Alizarin New-HTMLTableCondition -Name 'Laps' -ComparisonType string -Operator eq -Value $false -BackgroundColor Alizarin -HighlightHeaders LapsExpirationDays, LapsExpirationTime New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 60 -BackgroundColor Alizarin -HighlightHeaders LastLogonDays, LastLogonDate -FailBackgroundColor LimeGreen New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator ge -Value 0 -BackgroundColor LimeGreen -HighlightHeaders PasswordLastSet, PasswordLastChangedDays New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 300 -BackgroundColor Orange -HighlightHeaders PasswordLastSet, PasswordLastChangedDays New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 360 -BackgroundColor Alizarin -HighlightHeaders PasswordLastSet, PasswordLastChangedDays } } } } $Script:ConfigurationLAPSACL = [ordered] @{Name = 'LAPS ACL' Enabled = $true Execute = { Get-WinADComputerACLLAPS -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = {} Summary = { New-HTMLText -Text @("This report focuses on detecting whether computer has ability to read/write to LAPS properties in Active Directory. " "Often for many reasons such as broken ACL inheritance or not fully implemented SELF write access to LAPS - LAPS is implemented only partially. " "This means while IT may be thinking that LAPS should be functioning properly - the computer itself may not have rights to write password back to AD, making LAPS not functional. ") -FontSize 10pt -LineBreak New-HTMLText -Text "Following computer resources are exempt from LAPS: " -FontSize 10pt New-HTMLList { New-HTMLListItem -Text "Domain Controllers and Read Only Domain Controllers" New-HTMLListItem -Text 'Computer Service accounts such as AZUREADSSOACC$' } -FontSize 10pt New-HTMLText -Text 'Everything else should have proper LAPS ACL for the computer to provide data.' -FontSize 10pt } Variables = @{} Solution = { New-HTMLSection -Invisible { New-HTMLPanel { $Script:Reporting['LAPSACL']['Summary'] } New-HTMLPanel {} } if ($Script:Reporting['LAPSACL']['Data']) { New-HTMLSection -Name 'LAPS ACL Summary' { New-HTMLTable -DataTable $Script:Reporting['LAPSACL']['Data'] -Filtering { New-HTMLTableConditionGroup -Logic AND { New-HTMLTableCondition -Name 'LapsACL' -ComparisonType string -Operator eq -Value $true New-HTMLTableCondition -Name 'LapsExpirationACL' -ComparisonType string -Operator eq -Value $true New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $false } -BackgroundColor LimeGreen -HighlightHeaders LapsACL, LapsExpirationACL New-HTMLTableConditionGroup -Logic AND { New-HTMLTableCondition -Name 'LapsACL' -ComparisonType string -Operator eq -Value $false New-HTMLTableCondition -Name 'LapsExpirationACL' -ComparisonType string -Operator eq -Value $false New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $false } -BackgroundColor Alizarin -HighlightHeaders LapsACL, LapsExpirationACL New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor BlizzardBlue New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $false -BackgroundColor LimeGreen -FailBackgroundColor BlizzardBlue New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $true -BackgroundColor BlizzardBlue -HighlightHeaders LapsACL, LapsExpirationACL } } if ($Script:Reporting['LAPSACL']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['LAPSACL']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } -PagingOptions 10, 20, 30, 40, 50 } } } } } $Script:ConfigurationLAPSAndBitlocker = [ordered] @{Name = 'LAPS and BITLOCKER' Enabled = $true Execute = { Get-WinADBitlockerLapsSummary } Processing = {} Summary = {} Variables = @{} Solution = { if ($Script:Reporting['LapsAndBitLocker']['Data']) { New-HTMLChart { New-ChartLegend -LegendPosition bottom -HorizontalAlign center -Color Red, Blue, Yellow New-ChartTheme -Palette palette5 foreach ($Object in $DataTable) { New-ChartRadial -Name $Object.Name -Value $Object.Money } } New-HTMLTable -DataTable $Script:Reporting['LapsAndBitLocker']['Data'] -Filtering { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor BlizzardBlue New-HTMLTableCondition -Name 'LapsExpirationDays' -ComparisonType number -Operator lt -Value 0 -BackgroundColor BurntOrange -HighlightHeaders LapsExpirationDays, LapsExpirationTime -FailBackgroundColor LimeGreen New-HTMLTableCondition -Name 'Laps' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor Alizarin New-HTMLTableCondition -Name 'Laps' -ComparisonType string -Operator eq -Value $false -BackgroundColor Alizarin -HighlightHeaders LapsExpirationDays, LapsExpirationTime New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 60 -BackgroundColor Alizarin -HighlightHeaders LastLogonDays, LastLogonDate -FailBackgroundColor LimeGreen New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator ge -Value 0 -BackgroundColor LimeGreen -HighlightHeaders PasswordLastSet, PasswordLastChangedDays New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 300 -BackgroundColor Orange -HighlightHeaders PasswordLastSet, PasswordLastChangedDays New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -Operator gt -Value 360 -BackgroundColor Alizarin -HighlightHeaders PasswordLastSet, PasswordLastChangedDays } } } } $Script:ConfigurationServiceAccounts = [ordered] @{Name = 'Service Accounts' Enabled = $true Execute = { Get-WinADServiceAccount -PerDomain } Processing = {} Summary = {} Variables = @{} Solution = { if ($Script:Reporting['ServiceAccounts']['Data'] -is [System.Collections.IDictionary]) { New-HTMLTabPanel { foreach ($Domain in $Script:Reporting['ServiceAccounts']['Data'].Keys) { New-HTMLTab -Name $Domain { New-HTMLTable -DataTable $Script:Reporting['ServiceAccounts']['Data'][$Domain] -Filtering {} } } } } } } $Script:ShowWinADComputer = [ordered] @{Name = 'All Computers' Enabled = $true Execute = { Get-WinADComputers -PerDomain } Processing = {} Summary = {} Variables = @{} Solution = { if ($Script:Reporting['Computers']['Data'] -is [System.Collections.IDictionary]) { New-HTMLTabPanel { foreach ($Domain in $Script:Reporting['Computers']['Data'].Keys) { New-HTMLTab -Name $Domain { New-HTMLTable -DataTable $Script:Reporting['Computers']['Data'][$Domain] -Filtering {} } } } } } } $Script:ShowWinADUser = [ordered] @{Name = 'All Users' Enabled = $true Execute = { Get-WinADUsers -PerDomain } Processing = {} Variables = @{} Summary = {} Solution = { if ($Script:Reporting['Users']['Data'] -is [System.Collections.IDictionary]) { New-HTMLTabPanel -Orientation horizontal { foreach ($Domain in $Script:Reporting['Users']['Data'].Keys) { New-HTMLTab -Name $Domain { New-HTMLTable -DataTable $Script:Reporting['Users']['Data'][$Domain] -Filtering {} } } } } } } function Convert-BinaryToIP { [cmdletBinding()] param([string] $Binary) $Binary = $Binary -replace '\s+' if ($Binary.Length % 8) { Write-Warning -Message "Convert-BinaryToIP - Binary string '$Binary' is not evenly divisible by 8." return $Null } [int] $NumberOfBytes = $Binary.Length / 8 $Bytes = @(foreach ($i in 0..($NumberOfBytes - 1)) { try { [System.Convert]::ToByte($Binary.Substring(($i * 8), 8), 2) } catch { Write-Warning -Message "Convert-BinaryToIP - Error converting '$Binary' to bytes. `$i was $i." return $Null } }) return $Bytes -join '.' } function Convert-IPToBinary { [cmdletBinding()] param([string] $IP) $IPv4Regex = '(?:(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)\.){3}(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)' $IP = $IP.Trim() if ($IP -match "\A${IPv4Regex}\z") { try { return ($IP.Split('.') | ForEach-Object { [System.Convert]::ToString([byte] $_, 2).PadLeft(8, '0') }) -join '' } catch { Write-Warning -Message "Convert-IPToBinary - Error converting '$IP' to a binary string: $_" return $Null } } else { Write-Warning -Message "Convert-IPToBinary - Invalid IP detected: '$IP'. Conversion failed." return $Null } } function Convert-TrustForestTrustInfo { [CmdletBinding()] param([byte[]] $msDSTrustForestTrustInfo) $Flags = [ordered] @{'0' = 'Enabled' 'LsaTlnDisabledNew' = 'Not yet enabled' 'LsaTlnDisabledAdmin' = 'Disabled by administrator' 'LsaTlnDisabledConflict' = 'Disabled due to a conflict with another trusted domain' 'LsaSidDisabledAdmin' = 'Disabled for SID, NetBIOS, and DNS name–based matches by the administrator' 'LsaSidDisabledConflict' = 'Disabled for SID, NetBIOS, and DNS name–based matches due to a SID or DNS name–based conflict with another trusted domain' 'LsaNBDisabledAdmin' = 'Disabled for NetBIOS name–based matches by the administrator' 'LsaNBDisabledConflict' = 'Disabled for NetBIOS name–based matches due to a NetBIOS domain name conflict with another trusted domain' } if ($msDSTrustForestTrustInfo) { $Read = Get-ForestTrustInfo -Byte $msDSTrustForestTrustInfo $ForestTrustDomainInfo = [ordered]@{} [Array] $Records = foreach ($Record in $Read.Records) { if ($Record.RecordType -ne 'ForestTrustDomainInfo') { if ($Record.RecordType -eq 'ForestTrustTopLevelName') { $Type = 'Included' } else { $Type = 'Excluded' } [PSCustomObject] @{DnsName = $null NetbiosName = $null Sid = $null Type = $Type Suffix = $Record.ForestTrustData Status = $Flags["$($Record.Flags)"] StatusFlag = $Record.Flags WhenCreated = $Record.Timestamp } } else { $ForestTrustDomainInfo['DnsName'] = $Record.ForestTrustData.DnsName $ForestTrustDomainInfo['NetbiosName'] = $Record.ForestTrustData.NetbiosName $ForestTrustDomainInfo['Sid'] = $Record.ForestTrustData.Sid } } foreach ($Record in $Records) { $Record.DnsName = $ForestTrustDomainInfo['DnsName'] $Record.NetbiosName = $ForestTrustDomainInfo['NetbiosName'] $Record.Sid = $ForestTrustDomainInfo['Sid'] } $Records } } function ConvertTo-ComputerFQDN { [cmdletBinding()] param([string] $Computer) $IPAddressCheck = [System.Net.IPAddress]::TryParse($Computer, [ref][ipaddress]::Any) $IPAddressMatch = $Computer -match '^(\d+\.){3}\d+$' if ($IPAddressCheck -and $IPAddressMatch) { [Array] $ADServerFQDN = (Resolve-DnsName -Name $Computer -ErrorAction SilentlyContinue -Type PTR -Verbose:$false) if ($ADServerFQDN.Count -gt 0) { $ServerName = $ADServerFQDN[0].NameHost } else { $ServerName = $Computer } } else { [Array] $ADServerFQDN = (Resolve-DnsName -Name $Computer -ErrorAction SilentlyContinue -Type A -Verbose:$false) if ($ADServerFQDN.Count -gt 0) { $ServerName = $ADServerFQDN[0].Name } else { $ServerName = $Computer } } $ServerName } function ConvertTo-Date { [cmdletBinding()] Param ([Parameter(ValueFromPipeline, Mandatory)]$AccountExpires) process { $lngValue = $AccountExpires if (($lngValue -eq 0) -or ($lngValue -gt [DateTime]::MaxValue.Ticks)) { $AccountExpirationDate = $null } else { $Date = [DateTime]$lngValue $AccountExpirationDate = $Date.AddYears(1600).ToLocalTime() } $AccountExpirationDate } } function Get-ADConfigurationPermission { [cmdletBinding()] param([System.Collections.IDictionary]$ADObjectSplat, [string] $ObjectType, [switch] $FilterOut, [switch] $Owner) try { $Objects = Get-ADObject @ADObjectSplat -ErrorAction Stop } catch { Write-Warning "Get-ADConfigurationPermission - LDAP Filter: $($ADObjectSplat.LDAPFilter), SearchBase: $($ADObjectSplat.SearchBase)), Error: $($_.Exception.Message)" } foreach ($O in $Objects) { if ($FilterOut) { if ($ObjectType -eq 'site') { if ($O.DistinguishedName -like '*CN=Subnets,CN=Sites,CN=Configuration*') { continue } if ($O.DistinguishedName -like '*CN=Inter-Site Transports,CN=Sites,CN=Configuration*') { continue } } } if ($Owner) { Write-Verbose "Get-ADConfigurationPermission - Getting Owner from $($O.DistinguishedName)" $OwnerACL = Get-ADACLOwner -ADObject $O.DistinguishedName -Resolve [PSCustomObject] @{Name = $O.Name CanonicalName = $O.CanonicalName ObjectType = $ObjectType ObjectClass = $O.ObjectClass Owner = $OwnerACL.Owner OwnerName = $OwnerACL.OwnerName OwnerType = $OwnerACL.OwnerType WhenCreated = $O.WhenCreated WhenChanged = $O.WhenChanged DistinguishedName = $O.DistinguishedName } } else { Get-ADACL -ADObject $O.DistinguishedName -ResolveTypes } } } function Get-ADSubnet { [cmdletBinding()] param([Array] $Subnets, [switch] $AsHashTable) foreach ($Subnet in $Subnets) { if ($Subnet.SiteObject) { $SiteObject = ConvertFrom-DistinguishedName -DistinguishedName $Subnet.SiteObject } else { $SiteObject = '' } $Addr = $Subnet.Name.Split('/') $Address = [PSCustomObject] @{IP = $Addr[0] NetworkLength = $Addr[1] } try { $IPAddress = ([IPAddress] $Address.IP) } catch { Write-Warning "Get-ADSubnet - Conversion to IP failed. Error: $($_.Exception.Message)" } if ($IPAddress.AddressFamily -eq 'InterNetwork') { $AddressRange = Get-IPAddressRangeInformation -CIDRObject $Address $MaskBits = ([int](($Subnet.Name -split "/")[1])) if ($AsHashTable) { [ordered] @{Name = $Subnet.Name Type = 'IPv4' SiteName = $SiteObject SiteStatus = if ($SiteObject) { $true } else { $false } OverLap = $null OverLapList = $null Subnet = ([IPAddress](($Subnet.Name -split "/")[0])) MaskBits = ([int](($Subnet.Name -split "/")[1])) SubnetMask = ([IPAddress]"$([system.convert]::ToInt64(("1"*$MaskBits).PadRight(32,"0"),2))") TotalHosts = $AddressRange.TotalHosts UsableHosts = $AddressRange.UsableHosts HostMin = $AddressRange.HostMin HostMax = $AddressRange.HostMax Broadcast = $AddressRange.Broadcast } } else { [PSCustomObject] @{Name = $Subnet.Name Type = 'IPv4' SiteName = $SiteObject SiteStatus = if ($SiteObject) { $true } else { $false } Subnet = ([IPAddress](($Subnet.Name -split "/")[0])) MaskBits = ([int](($Subnet.Name -split "/")[1])) SubnetMask = ([IPAddress]"$([system.convert]::ToInt64(("1"*$MaskBits).PadRight(32,"0"),2))") TotalHosts = $AddressRange.TotalHosts UsableHosts = $AddressRange.UsableHosts HostMin = $AddressRange.HostMin HostMax = $AddressRange.HostMax Broadcast = $AddressRange.Broadcast } } } else { $AddressRange = $null if ($AsHashTable) { [ordered] @{Name = $Subnet.Name Type = 'IPv6' SiteName = $SiteObject SiteStatus = if ($SiteObject) { $true } else { $false } OverLap = $null OverLapList = $null Subnet = ([IPAddress](($Subnet.Name -split "/")[0])) MaskBits = ([int](($Subnet.Name -split "/")[1])) SubnetMask = $null TotalHosts = $AddressRange.TotalHosts UsableHosts = $AddressRange.UsableHosts HostMin = $AddressRange.HostMin HostMax = $AddressRange.HostMax Broadcast = $AddressRange.Broadcast } } else { [PSCustomObject] @{Name = $Subnet.Name Type = 'IPv6' SiteName = $SiteObject SiteStatus = if ($SiteObject) { $true } else { $false } Subnet = ([IPAddress](($Subnet.Name -split "/")[0])) MaskBits = ([int](($Subnet.Name -split "/")[1])) SubnetMask = $null TotalHosts = $AddressRange.TotalHosts UsableHosts = $AddressRange.UsableHosts HostMin = $AddressRange.HostMin HostMax = $AddressRange.HostMax Broadcast = $AddressRange.Broadcast } } } } } function Get-ForestTrustInfo { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Byte An array of bytes which describes the forest trust information. .EXAMPLE An example .NOTES Author: Chris Dent #> [CmdletBinding()] param ([Parameter(Mandatory)][byte[]]$Byte) $reader = [System.IO.BinaryReader][System.IO.MemoryStream]$Byte $trustInfo = [PSCustomObject]@{Version = $reader.ReadUInt32() RecordCount = $reader.ReadUInt32() Records = $null } $trustInfo.Records = for ($i = 0; $i -lt $trustInfo.RecordCount; $i++) { Get-ForestTrustRecord -BinaryReader $reader } $trustInfo } function Get-ForestTrustRecord { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER BinaryReader Parameter description .EXAMPLE An example .NOTES Author: Chris Dent #> [CmdletBinding()] param ([Parameter(Mandatory)][System.IO.BinaryReader]$BinaryReader) [Flags()] enum TrustFlags { LsaTlnDisabledNew = 0x1 LsaTlnDisabledAdmin = 0x2 LsaTlnDisabledConflict = 0x4 } [Flags()] enum ForestTrustFlags { LsaSidDisabledAdmin = 0x1 LsaSidDisabledConflict = 0x2 LsaNBDisabledAdmin = 0x4 LsaNBDisabledConflict = 0x8 } enum RecordType { ForestTrustTopLevelName ForestTrustTopLevelNameEx ForestTrustDomainInfo } $record = [PSCustomObject]@{RecordLength = $BinaryReader.ReadUInt32() Flags = $BinaryReader.ReadUInt32() Timestamp = $BinaryReader.ReadUInt32(), $BinaryReader.ReadUInt32() RecordType = $BinaryReader.ReadByte() -as [RecordType] ForestTrustData = $null } $record.Timestamp = [DateTime]::FromFileTimeUtc(($record.Timestamp[0] -as [UInt64] -shl 32) + $record.Timestamp[1]) $record.Flags = switch ($record.RecordType) { ([RecordType]::ForestTrustDomainInfo) { $record.Flags -as [ForestTrustFlags] } default { $record.Flags -as [TrustFlags] } } if ($record.RecordLength -gt 11) { switch ($record.RecordType) { ([RecordType]::ForestTrustDomainInfo) { $record.ForestTrustData = [PSCustomObject]@{Sid = $null DnsName = $null NetbiosName = $null } $sidLength = $BinaryReader.ReadUInt32() if ($sidLength -gt 0) { $record.ForestTrustData.Sid = [System.Security.Principal.SecurityIdentifier]::new($BinaryReader.ReadBytes($sidLength), 0) } $dnsNameLen = $BinaryReader.ReadUInt32() if ($dnsNameLen -gt 0) { $record.ForestTrustData.DnsName = [string]::new($BinaryReader.ReadBytes($dnsNameLen) -as [char[]]) } $NetbiosNameLen = $BinaryReader.ReadUInt32() if ($NetbiosNameLen -gt 0) { $record.ForestTrustData.NetbiosName = [string]::new($BinaryReader.ReadBytes($NetbiosNameLen) -as [char[]]) } } default { $nameLength = $BinaryReader.ReadUInt32() if ($nameLength -gt 0) { $record.ForestTrustData = [String]::new($BinaryReader.ReadBytes($nameLength) -as [char[]]) } } } } $record } function Get-GitHubVersion { [cmdletBinding()] param([Parameter(Mandatory)][string] $Cmdlet, [Parameter(Mandatory)][string] $RepositoryOwner, [Parameter(Mandatory)][string] $RepositoryName) $App = Get-Command -Name $Cmdlet -ErrorAction SilentlyContinue if ($App) { [Array] $GitHubReleases = (Get-GitHubLatestRelease -Url "https://api.github.com/repos/$RepositoryOwner/$RepositoryName/releases" -Verbose:$false) $LatestVersion = $GitHubReleases[0] if (-not $LatestVersion.Errors) { if ($App.Version -eq $LatestVersion.Version) { "Current/Latest: $($LatestVersion.Version) at $($LatestVersion.PublishDate)" } elseif ($App.Version -lt $LatestVersion.Version) { "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Update?" } elseif ($App.Version -gt $LatestVersion.Version) { "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Lucky you!" } } else { "Current: $($App.Version)" } } } function Get-IPAddressRangeInformation { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER CIDRObject Parameter description .EXAMPLE An example .NOTES #> [cmdletBinding()] param([psobject] $CIDRObject) $IPv4Regex = '(?:(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)\.){3}(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)' $o = [ordered] @{} $o.IP = [string] $CIDRObject.IP $o.BinaryIP = Convert-IPToBinary $o.IP if (-not $o.BinaryIP) { return } $o.NetworkLength = [int32] $CIDRObject.NetworkLength $o.SubnetMask = Convert-BinaryToIP ('1' * $o.NetworkLength).PadRight(32, '0') $o.BinarySubnetMask = ('1' * $o.NetworkLength).PadRight(32, '0') $o.BinaryNetworkAddress = $o.BinaryIP.SubString(0, $o.NetworkLength).PadRight(32, '0') if ($Contains) { if ($Contains -match "\A${IPv4Regex}\z") { return Test-IPIsInNetwork $Contains $o.BinaryNetworkAddress $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1') } else { Write-Error "Get-IPAddressRangeInformation - Invalid IPv4 address specified with -Contains" return } } $o.NetworkAddress = Convert-BinaryToIP $o.BinaryNetworkAddress if ($o.NetworkLength -eq 32 -or $o.NetworkLength -eq 31) { $o.HostMin = $o.IP } else { $o.HostMin = Convert-BinaryToIP ([System.Convert]::ToString(([System.Convert]::ToInt64($o.BinaryNetworkAddress, 2) + 1), 2)).PadLeft(32, '0') } [string] $BinaryBroadcastIP = $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1') $o.BinaryBroadcast = $BinaryBroadcastIP [int64] $DecimalHostMax = [System.Convert]::ToInt64($BinaryBroadcastIP, 2) - 1 [string] $BinaryHostMax = [System.Convert]::ToString($DecimalHostMax, 2).PadLeft(32, '0') $o.HostMax = Convert-BinaryToIP $BinaryHostMax $o.TotalHosts = [int64][System.Convert]::ToString(([System.Convert]::ToInt64($BinaryBroadcastIP, 2) - [System.Convert]::ToInt64($o.BinaryNetworkAddress, 2) + 1)) $o.UsableHosts = $o.TotalHosts - 2 if ($o.NetworkLength -eq 32) { $o.Broadcast = $Null $o.UsableHosts = [int64] 1 $o.TotalHosts = [int64] 1 $o.HostMax = $o.IP } elseif ($o.NetworkLength -eq 31) { $o.Broadcast = $Null $o.UsableHosts = [int64] 2 $o.TotalHosts = [int64] 2 [int64] $DecimalHostMax2 = [System.Convert]::ToInt64($BinaryBroadcastIP, 2) [string] $BinaryHostMax2 = [System.Convert]::ToString($DecimalHostMax2, 2).PadLeft(32, '0') $o.HostMax = Convert-BinaryToIP $BinaryHostMax2 } elseif ($o.NetworkLength -eq 30) { $o.UsableHosts = [int64] 2 $o.TotalHosts = [int64] 4 $o.Broadcast = Convert-BinaryToIP $BinaryBroadcastIP } else { $o.Broadcast = Convert-BinaryToIP $BinaryBroadcastIP } if ($Enumerate) { $IPRange = @(Get-IPRange $o.BinaryNetworkAddress $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1')) if ((31, 32) -notcontains $o.NetworkLength) { $IPRange = $IPRange[1..($IPRange.Count - 1)] $IPRange = $IPRange[0..($IPRange.Count - 2)] } $o.IPEnumerated = $IPRange } else { $o.IPEnumerated = @() } [PSCustomObject]$o } function Get-IPRange { [cmdletBinding()] param([string] $StartBinary, [string] $EndBinary) [int64] $StartInt = [System.Convert]::ToInt64($StartBinary, 2) [int64] $EndInt = [System.Convert]::ToInt64($EndBinary, 2) for ($BinaryIP = $StartInt; $BinaryIP -le $EndInt; $BinaryIP++) { Convert-BinaryToIP ([System.Convert]::ToString($BinaryIP, 2).PadLeft(32, '0')) } } 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 } } } } function Get-WinADTrustObject { [cmdletBinding()] param([Parameter(Mandatory, Position = 0)][alias('Domain')][string] $Identity, [switch] $AsHashTable) $Summary = [ordered] @{} $TrustType = @{CrossLink = 'The trust relationship is a shortcut between two domains that exists to optimize the authentication processing between two domains that are in separate domain trees.' External = 'The trust relationship is with a domain outside of the current forest.' Forest = 'The trust relationship is between two forest root domains in separate Windows Server 2003 forests.' Kerberos = 'The trusted domain is an MIT Kerberos realm.' ParentChild = 'The trust relationship is between a parent and a child domain.' TreeRoot = 'One of the domains in the trust relationship is a tree root.' Unknown = 'The trust is a non-specific type.' } $TrustDirection = @{Bidirectional = 'Each domain or forest has access to the resources of the other domain or forest.' Inbound = 'This is a trusting domain or forest. The other domain or forest has access to the resources of this domain or forest. This domain or forest does not have access to resources that belong to the other domain or forest.' Outbound = 'This is a trusted domain or forest. This domain or forest has access to resources of the other domain or forest. The other domain or forest does not have access to the resources of this domain or forest.' } if ($Identity -contains 'DC=') { $DomainName = "LDAP://$Domain" $TrustSource = ConvertFrom-DistinguishedName -DistinguishedName $DomainName -ToDomainCN } else { $DomainDN = ConvertTo-DistinguishedName -CanonicalName $Identity -ToDomain $DomainName = "LDAP://$DomainDN" $TrustSource = $Identity } $searcher = [adsisearcher]'(objectClass=trustedDomain)' $searcher.SearchRoot = [adsi] $DomainName $Trusts = $searcher.FindAll() foreach ($Trust in $Trusts) { $TrustD = [System.DirectoryServices.ActiveDirectory.TrustDirection] $Trust.properties.trustdirection[0] $TrustT = [System.DirectoryServices.ActiveDirectory.TrustType] $Trust.properties.trusttype[0] if ($Trust.properties.'msds-trustforesttrustinfo') { $msDSTrustForestTrustInfo = Convert-TrustForestTrustInfo -msDSTrustForestTrustInfo $Trust.properties.'msds-trustforesttrustinfo'[0] } else { $msDSTrustForestTrustInfo = $null } if ($Trust.properties.trustattributes) { $TrustAttributes = Get-ADTrustAttributes -Value ([int] $Trust.properties.trustattributes[0]) } else { $TrustAttributes = $null } if ($Trust.properties.securityidentifier) { try { $ObjectSID = [System.Security.Principal.SecurityIdentifier]::new($Trust.properties.securityidentifier[0], 0).Value } catch { $ObjectSID = $null } } else { $ObjectSID = $null } $TrustObject = [PSCustomObject] @{TrustSource = $TrustSource TrustPartner = [string] $Trust.properties.trustpartner TrustPartnerNetBios = [string] $Trust.properties.flatname TrustDirection = $TrustD.ToString() TrustType = $TrustT.ToString() TrustAttributes = $TrustAttributes TrustDirectionText = $TrustDirection[$TrustD.ToString()] TrustTypeText = $TrustType[$TrustT.ToString()] WhenCreated = [DateTime] $Trust.properties.whencreated[0] WhenChanged = [DateTime] $Trust.properties.whenchanged[0] ObjectSID = $ObjectSID Distinguishedname = [string] $Trust.properties.distinguishedname IsCriticalSystemObject = [bool]::Parse($Trust.properties.iscriticalsystemobject[0]) ObjectGuid = [guid]::new($Trust.properties.objectguid[0]) ObjectCategory = [string] $Trust.properties.objectcategory ObjectClass = ([array] $Trust.properties.objectclass)[-1] UsnCreated = [string] $Trust.properties.usncreated UsnChanged = [string] $Trust.properties.usnchanged ShowInAdvancedViewOnly = [bool]::Parse($Trust.properties.showinadvancedviewonly) TrustPosixOffset = [string] $Trust.properties.trustposixoffset msDSTrustForestTrustInfo = $msDSTrustForestTrustInfo msDSSupportedEncryptionTypes = if ($Trust.properties.'msds-supportedencryptiontypes') { Get-ADEncryptionTypes -Value ([int] $Trust.properties.'msds-supportedencryptiontypes'[0]) } else { $null } } if ($AsHashTable) { $Summary[$TrustObject.trustpartner] = $TrustObject } else { $TrustObject } } if ($AsHashTable) { $Summary } } $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 -Writable $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 New-HTMLGroupDiagramDefault { [cmdletBinding()] param([Array] $ADGroup, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [switch] $HideComputers, [switch] $HideUsers, [switch] $HideOther, [string] $DataTableID, [int] $ColumnID, [switch] $Online) New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($ADGroup) { foreach ($ADObject in $ADGroup) { $ID = "$($ADObject.DomainName)$($ADObject.Name)" $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)" if ($ADObject.Type -eq 'User') { if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user -IconColor LightSteelBlue } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsToEnabled -Dashes } } elseif ($ADObject.Type -eq 'Group') { if ($ADObject.Nesting -eq -1) { $BorderColor = 'Red' $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg' } else { $BorderColor = 'Blue' $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg' } $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers) $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user-friends -IconColor VeryLightGrey } New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsToEnabled } elseif ($ADObject.Type -eq 'Computer') { if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsToEnabled -Dashes } } else { if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsToEnabled -Dashes } } } } } } function New-HTMLGroupDiagramHierachical { [cmdletBinding()] param([Array] $ADGroup, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [switch] $HideComputers, [switch] $HideUsers, [switch] $HideOther, [switch] $Online) New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsLayout -HierarchicalEnabled $true New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200 if ($ADGroup) { foreach ($ADObject in $ADGroup) { [int] $Level = $($ADObject.Nesting) + 1 $ID = "$($ADObject.DomainName)$($ADObject.Name)$Level" [int] $LevelParent = $($ADObject.Nesting) $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)$LevelParent" [int] $Level = $($ADObject.Nesting) + 1 if ($ADObject.Type -eq 'User') { if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user -IconColor LightSteelBlue } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsToEnabled -Dashes } } elseif ($ADObject.Type -eq 'Group') { if ($ADObject.Nesting -eq -1) { $BorderColor = 'LightGreen' $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg' $IconSolid = 'user-friends' } elseif ($ADObject.CircularIndirect -eq $true -or $ADObject.CircularDirect -eq $true) { $Image = 'https://www.flaticon.com/svg/static/icons/svg/3039/3039356.svg' $BorderColor = 'PaleVioletRed' $IconSolid = 'circle-notch' } else { $BorderColor = 'VeryLightGrey' $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg' $IconSolid = 'users' } $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers) if ($ADObject.CircularIndirect -eq $true -or $ADObject.CircularDirect -eq $true) { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers + [System.Environment]::NewLine + "Circular: $True" } else { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers } if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -Level $Level -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid $IconSolid -IconColor $BorderColor } New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsToEnabled } elseif ($ADObject.Type -eq 'Computer') { if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray -Level $Level } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsToEnabled -Dashes } } else { if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon -Level $Level } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsToEnabled -Dashes } } } } } } function New-HTMLGroupDiagramSummary { [cmdletBinding()] param([Array] $ADGroup, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [switch] $HideComputers, [switch] $HideUsers, [switch] $HideOther, [string] $DataTableID, [int] $ColumnID, [switch] $Online) $ConnectionsTracker = @{} New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($ADGroup) { foreach ($ADObject in $ADGroup) { $ID = "$($ADObject.DomainName)$($ADObject.Name)" $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)" if (-not $ConnectionsTracker[$ID]) { $ConnectionsTracker[$ID] = @{} } if (-not $ConnectionsTracker[$ID][$IDParent]) { if ($ADObject.Type -eq 'User') { if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user -IconColor LightSteelBlue } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsToEnabled -Dashes } } elseif ($ADObject.Type -eq 'Group') { if ($ADObject.Nesting -eq -1) { $BorderColor = 'Red' $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg' } else { $BorderColor = 'Blue' $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg' } $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers) $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -ArrowsToEnabled -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -ArrowsToEnabled -IconSolid user-friends -IconColor VeryLightGrey } New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsToEnabled } elseif ($ADObject.Type -eq 'Computer') { if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsToEnabled -Dashes } } else { if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsToEnabled -Dashes } } $ConnectionsTracker[$ID][$IDParent] = $true } } } } } function New-HTMLGroupDiagramSummaryHierarchical { [cmdletBinding()] param([Array] $ADGroup, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [switch] $HideComputers, [switch] $HideUsers, [switch] $HideOther, [switch] $Online) New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsLayout -HierarchicalEnabled $true New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200 if ($ADGroup) { foreach ($ADObject in $ADGroup) { $ID = "$($ADObject.DomainName)$($ADObject.Name)" $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)" [int] $Level = $($ADObject.Nesting) + 1 if ($ADObject.Type -eq 'User') { if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user -IconColor LightSteelBlue } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsToEnabled -Dashes } } elseif ($ADObject.Type -eq 'Group') { if ($ADObject.Nesting -eq -1) { $BorderColor = 'Red' $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg' } else { $BorderColor = 'Blue' $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg' } $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers) $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -Level $Level -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user-friends } New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsToEnabled } elseif ($ADObject.Type -eq 'Computer') { if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray -Level $Level } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsToEnabled -Dashes } } else { if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon -Level $Level } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsToEnabled -Dashes } } } } } } function New-HTMLGroupOfDiagramDefault { [cmdletBinding()] param([Array] $Identity, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [switch] $HideComputers, [switch] $HideUsers, [switch] $HideOther, [string] $DataTableID, [int] $ColumnID, [switch] $Online) New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($Identity) { foreach ($ADObject in $Identity) { $ID = "$($ADObject.DomainName)$($ADObject.Name)" $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)" if ($ADObject.Type -eq 'User') { if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user -IconColor LightSteelBlue } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsFromEnabled -Dashes } } elseif ($ADObject.Type -eq 'Group') { if ($ADObject.Nesting -eq -1) { $BorderColor = 'Red' $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg' } else { $BorderColor = 'Blue' $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg' } $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user-friends -IconColor VeryLightGrey } New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsFromEnabled } elseif ($ADObject.Type -eq 'Computer') { if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsFromEnabled -Dashes } } else { if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsFromEnabled -Dashes } } } } } } function New-HTMLGroupOfDiagramHierarchical { [cmdletBinding()] param([Array] $Identity, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [switch] $HideComputers, [switch] $HideUsers, [switch] $HideOther, [switch] $Online) New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsLayout -HierarchicalEnabled $true New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200 if ($Identity) { foreach ($ADObject in $Identity) { [int] $Level = $($ADObject.Nesting) + 1 $ID = "$($ADObject.DomainName)$($ADObject.Name)$Level" [int] $LevelParent = $($ADObject.Nesting) $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)$LevelParent" [int] $Level = $($ADObject.Nesting) + 1 if ($ADObject.Type -eq 'User') { if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user -IconColor LightSteelBlue } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsFromEnabled -Dashes } } elseif ($ADObject.Type -eq 'Group') { if ($ADObject.Nesting -eq -1) { $BorderColor = 'Red' $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg' } else { $BorderColor = 'Blue' $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg' } $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -Level $Level -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user-friends } New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsFromEnabled } elseif ($ADObject.Type -eq 'Computer') { if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray -Level $Level } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsFromEnabled -Dashes } } else { if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon -Level $Level } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsFromEnabled -Dashes } } } } } } function New-HTMLGroupOfDiagramSummary { [cmdletBinding()] param([Array] $ADGroup, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [switch] $HideComputers, [switch] $HideUsers, [switch] $HideOther, [string] $DataTableID, [int] $ColumnID, [switch] $Online) $ConnectionsTracker = @{} New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($ADGroup) { foreach ($ADObject in $ADGroup) { $ID = "$($ADObject.DomainName)$($ADObject.Name)" $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)" if (-not $ConnectionsTracker[$ID]) { $ConnectionsTracker[$ID] = @{} } if (-not $ConnectionsTracker[$ID][$IDParent]) { if ($ADObject.Type -eq 'User') { if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user -IconColor LightSteelBlue } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsFromEnabled -Dashes } } elseif ($ADObject.Type -eq 'Group') { if ($ADObject.Nesting -eq -1) { $BorderColor = 'Red' $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg' } else { $BorderColor = 'Blue' $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg' } $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers) $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -IconSolid user-friends -IconColor VeryLightGrey } New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsFromEnabled } elseif ($ADObject.Type -eq 'Computer') { if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsFromEnabled -Dashes } } else { if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Default') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsFromEnabled -Dashes } } $ConnectionsTracker[$ID][$IDParent] = $true } } } } } function New-HTMLGroupOfDiagramSummaryHierarchical { [cmdletBinding()] param([Array] $ADGroup, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [switch] $HideComputers, [switch] $HideUsers, [switch] $HideOther, [switch] $Online) New-HTMLDiagram -Height 'calc(100vh - 200px)' { New-DiagramOptionsLayout -HierarchicalEnabled $true New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200 if ($ADGroup) { foreach ($ADObject in $ADGroup) { $ID = "$($ADObject.DomainName)$($ADObject.Name)" $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroup)" [int] $Level = $($ADObject.Nesting) + 1 if ($ADObject.Type -eq 'User') { if (-not $HideUsers -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3135/3135715.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user -IconColor LightSteelBlue } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Blue -ArrowsFromEnabled -Dashes } } elseif ($ADObject.Type -eq 'Group') { if ($ADObject.Nesting -eq -1) { $BorderColor = 'Red' $Image = 'https://image.flaticon.com/icons/svg/921/921347.svg' } else { $BorderColor = 'Blue' $Image = 'https://image.flaticon.com/icons/svg/166/166258.svg' } $SummaryMembers = -join ('Total: ', $ADObject.TotalMembers, ' Direct: ', $ADObject.DirectMembers, ' Groups: ', $ADObject.DirectGroups, ' Indirect: ', $ADObject.IndirectMembers) $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName + [System.Environment]::NewLine + $SummaryMembers if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image $Image -Level $Level -ColorBorder $BorderColor } else { New-DiagramNode -Id $ID -Label $Label -Level $Level -IconSolid user-friends } New-DiagramLink -ColorOpacity 0.5 -From $ID -To $IDParent -Color Orange -ArrowsFromEnabled } elseif ($ADObject.Type -eq 'Computer') { if (-not $HideComputers -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3003/3003040.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid desktop -IconColor LightGray -Level $Level } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Arsenic -ArrowsFromEnabled -Dashes } } else { if (-not $HideOther -or $HideAppliesTo -notin 'Both', 'Hierarchical') { $Label = $ADObject.Name + [System.Environment]::NewLine + $ADObject.DomainName if ($Online) { New-DiagramNode -Id $ID -Label $Label -Image 'https://image.flaticon.com/icons/svg/3347/3347551.svg' -Level $Level } else { New-DiagramNode -Id $ID -Label $Label -IconSolid robot -IconColor LightSalmon -Level $Level } New-DiagramLink -ColorOpacity 0.2 -From $ID -To $IDParent -Color Boulder -ArrowsFromEnabled -Dashes } } } } } } function Reset-ADEssentialsStatus { [cmdletBinding()] param() if (-not $Script:DefaultTypes) { $Script:DefaultTypes = foreach ($T in $Script:ADEssentialsConfiguration.Keys) { if ($Script:ADEssentialsConfiguration[$T].Enabled) { $T } } } else { foreach ($T in $Script:ADEssentialsConfiguration.Keys) { if ($Script:ADEssentialsConfiguration[$T]) { $Script:ADEssentialsConfiguration[$T]['Enabled'] = $false } } foreach ($T in $Script:DefaultTypes) { if ($Script:ADEssentialsConfiguration[$T]) { $Script:ADEssentialsConfiguration[$T]['Enabled'] = $true } } } } $Script:ADEssentialsConfiguration = [ordered] @{Users = $Script:ShowWinADUser Computers = $Script:ShowWinADComputer Laps = $Script:ConfigurationLAPS LapsACL = $Script:ConfigurationLAPSACL LapsAndBitLocker = $Script:ConfigurationLAPSAndBitlocker BitLocker = $Script:ConfigurationBitLocker ServiceAccounts = $Script:ConfigurationServiceAccounts } function Test-ADSubnet { [cmdletBinding()] param([Array] $Subnets) foreach ($Subnet in $Subnets) { if ($Subnet.Type -ne 'IPV4') { continue } $SmallSubnets = $Subnets | Where-Object { $_.MaskBits -gt $Subnet.MaskBits -and $Subnet.Type -ne 'IPV4' } foreach ($SmallSubnet in $SmallSubnets) { if (($SmallSubnet.Subnet.Address -band $Subnet.SubnetMask.Address) -eq $Subnet.Subnet.Address) { [PSCustomObject]@{Name = $Subnet.Name SiteName = $Subnet.SiteName SiteStatus = $Subnet.SiteStatus SubnetRange = $Subnet.Subnet OverlappingSubnet = $SmallSubnet.Name OverlappingSubnetRange = $SmallSubnet.Subnet SiteCollission = $Subnet.Name -ne $SmallSubnet.Name } } } } } function Test-DomainTrust { [cmdletBinding()] param([string] $Domain, [string] $TrustedDomain) $DomainInformation = Get-WinADDomain -Domain $Domain $DomainPDC = $DomainInformation.PdcRoleOwner.Name $PropertiesTrustWMI = @('FlatName', 'SID', 'TrustAttributes', 'TrustDirection', 'TrustedDCName', 'TrustedDomain', 'TrustIsOk', 'TrustStatus', 'TrustStatusString', 'TrustType') $getCimInstanceSplat = @{ClassName = 'Microsoft_DomainTrustStatus' Namespace = 'root\MicrosoftActiveDirectory' ComputerName = $DomainPDC ErrorAction = 'SilentlyContinue' Property = $PropertiesTrustWMI Verbose = $false } if ($TrustedDomain) { $getCimInstanceSplat['Filter'] = "TrustedDomain = `"$TrustedDomain`"" } $TrustStatatuses = Get-CimInstance @getCimInstanceSplat if ($TrustStatatuses) { foreach ($Status in $TrustStatatuses) { [PSCustomObject] @{'TrustSource' = $DomainInformation.Name 'TrustPartner' = $Status.TrustedDomain 'TrustAttributes' = if ($Status.TrustAttributes) { Get-ADTrustAttributes -Value $Status.TrustAttributes } else { 'Error - needs fixing' } 'TrustStatus' = if ($null -ne $Status) { $Status.TrustStatusString } else { 'N/A' } 'TrustSourceDC' = if ($null -ne $Status) { $Status.PSComputerName } else { '' } 'TrustTargetDC' = if ($null -ne $Status) { $Status.TrustedDCName.Replace('\\', '') } else { '' } } } } else { [PSCustomObject] @{'TrustSource' = $DomainInformation.Name 'TrustPartner' = $TrustedDomain 'TrustAttributes' = 'Error - needs fixing' 'TrustStatus' = 'N/A' 'TrustSourceDC' = '' 'TrustTargetDC' = '' } } } function Test-IPIsInNetwork { [cmdletBinding()] param([string] $IP, [string] $StartBinary, [string] $EndBinary) $TestIPBinary = Convert-IPToBinary $IP [int64] $TestIPInt64 = [System.Convert]::ToInt64($TestIPBinary, 2) [int64] $StartInt64 = [System.Convert]::ToInt64($StartBinary, 2) [int64] $EndInt64 = [System.Convert]::ToInt64($EndBinary, 2) if ($TestIPInt64 -ge $StartInt64 -and $TestIPInt64 -le $EndInt64) { return $True } else { return $False } } function Test-LDAPCertificate { [CmdletBinding()] param([string] $Computer, [int] $Port, [PSCredential] $Credential) $Date = Get-Date if ($Credential) { Write-Verbose "Test-LDAPCertificate - Certificate verification $Computer/$Port/Auth Basic" } else { Write-Verbose "Test-LDAPCertificate - Certificate verification $Computer/$Port/Auth Kerberos" } $Connection = $null $DirectoryIdentifier = [DirectoryServices.Protocols.LdapDirectoryIdentifier]::new($Computer, $Port) if ($psboundparameters.ContainsKey("Credential")) { $Connection = [DirectoryServices.Protocols.LdapConnection]::new($DirectoryIdentifier, $Credential.GetNetworkCredential()) $Connection.AuthType = [DirectoryServices.Protocols.AuthType]::Basic } else { $Connection = [DirectoryServices.Protocols.LdapConnection]::new($DirectoryIdentifier) $Connection.AuthType = [DirectoryServices.Protocols.AuthType]::Kerberos } $Connection.SessionOptions.ProtocolVersion = 3 $Connection.SessionOptions.SecureSocketLayer = $true New-Variable LdapCertificate -Scope Script -Force $Connection.SessionOptions.VerifyServerCertificate = { param([DirectoryServices.Protocols.LdapConnection]$Connection, [Security.Cryptography.X509Certificates.X509Certificate2]$Certificate) $Script:LdapCertificate = $Certificate return $true } $State = $true try { $Connection.Bind() $ErrorMessage = '' } catch { $State = $false $ErrorMessage = $_.Exception.Message.Trim() } $KeyExchangeAlgorithm = @{'0' = 'None' '43522' = 'DiffieHellman' '41984' = 'RsaKeyX' '9216' = 'RsaSign' '44550' = 'ECDH_Ephem' } if ($Script:LdapCertificate.NotBefore -is [DateTime]) { $X509NotBeforeDays = (New-TimeSpan -Start $Date -End $Script:LdapCertificate.NotBefore).Days } else { $X509NotBeforeDays = $null } if ($Script:LdapCertificate.NotAfter -is [DateTime]) { $X509NotAfterDays = (New-TimeSpan -Start $Date -End $Script:LdapCertificate.NotAfter).Days } else { $X509NotAfterDays = $null } $Certificate = [ordered]@{State = $State X509NotBeforeDays = $X509NotBeforeDays X509NotAfterDays = $X509NotAfterDays X509DnsNameList = $Script:LdapCertificate.DnsNameList.Unicode X509NotBefore = $Script:LdapCertificate.NotBefore X509NotAfter = $Script:LdapCertificate.NotAfter AlgorithmIdentifier = $Connection.SessionOptions.SslInformation.AlgorithmIdentifier CipherStrength = $Connection.SessionOptions.SslInformation.CipherStrength X509FriendlyName = $Script:LdapCertificate.FriendlyName X509SendAsTrustedIssuer = $Script:LdapCertificate.SendAsTrustedIssuer X509SerialNumber = $Script:LdapCertificate.SerialNumber X509Thumbprint = $Script:LdapCertificate.Thumbprint X509SubjectName = $Script:LdapCertificate.Subject X509Issuer = $Script:LdapCertificate.Issuer X509HasPrivateKey = $Script:LdapCertificate.HasPrivateKey X509Version = $Script:LdapCertificate.Version X509Archived = $Script:LdapCertificate.Archived Protocol = $Connection.SessionOptions.SslInformation.Protocol Hash = $Connection.SessionOptions.SslInformation.Hash HashStrength = $Connection.SessionOptions.SslInformation.HashStrength KeyExchangeAlgorithm = $KeyExchangeAlgorithm["$($Connection.SessionOptions.SslInformation.KeyExchangeAlgorithm)"] ExchangeStrength = $Connection.SessionOptions.SslInformation.ExchangeStrength ErrorMessage = $ErrorMessage } $Certificate } function Test-LDAPPorts { [CmdletBinding()] param([string] $ServerName, [int] $Port) if ($ServerName -and $Port -ne 0) { Write-Verbose "Test-LDAPPorts - Processing $ServerName / $Port" try { $LDAP = "LDAP://" + $ServerName + ':' + $Port $Connection = [ADSI]($LDAP) $Connection.Close() [PSCustomObject] @{Computer = $ServerName Port = $Port Status = $true ErrorMessage = '' } } catch { $ErrorMessage = $($_.Exception.Message) -replace [System.Environment]::NewLine if ($_.Exception.ToString() -match "The server is not operational") { Write-Warning "Test-LDAPPorts - Can't open $ServerName`:$Port. Error: $ErrorMessage" } elseif ($_.Exception.ToString() -match "The user name or password is incorrect") { Write-Warning "Test-LDAPPorts - Current user ($Env:USERNAME) doesn't seem to have access to to LDAP on port $Server`:$Port. Error: $ErrorMessage" } else { Write-Warning -Message "Test-LDAPPorts - Error: $ErrorMessage" } [PSCustomObject] @{Computer = $ServerName Port = $Port Status = $false ErrorMessage = $ErrorMessage } } } } function Test-LdapServer { [cmdletBinding()] param([string] $ServerName, [string] $Computer, [PSCustomObject] $Advanced) if ($ServerName -notlike '*.*') { $GlobalCatalogSSL = [PSCustomObject] @{Status = $false; ErrorMessage = 'No FQDN' } $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP $ConnectionLDAPS = [PSCustomObject] @{Status = $false; ErrorMessage = 'No FQDN' } $ConnectionLDAP = Test-LDAPPorts -ServerName $ServerName -Port $PortLDAP $PortsThatWork = @(if ($GlobalCatalogNonSSL.Status) { $GCPortLDAP } if ($GlobalCatalogSSL.Status) { $GCPortLDAPSSL } if ($ConnectionLDAP.Status) { $PortLDAP } if ($ConnectionLDAPS.Status) { $PortLDAPS }) | Sort-Object } else { $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.Status) { $GCPortLDAP } if ($GlobalCatalogSSL.Status) { $GCPortLDAPSSL } if ($ConnectionLDAP.Status) { $PortLDAP } if ($ConnectionLDAPS.Status) { $PortLDAPS }) | Sort-Object } if ($VerifyCertificate) { $Output = [ordered] @{Computer = $ServerName Site = $Advanced.Site IsRO = $Advanced.IsReadOnly IsGC = $Advanced.IsGlobalCatalog GlobalCatalogLDAP = $GlobalCatalogNonSSL.Status GlobalCatalogLDAPS = $GlobalCatalogSSL.Status GlobalCatalogLDAPSBind = $null LDAP = $ConnectionLDAP.Status LDAPS = $ConnectionLDAPS.Status LDAPSBind = $null AvailablePorts = $PortsThatWork -join ',' X509NotBeforeDays = $null X509NotAfterDays = $null X509DnsNameList = $null OperatingSystem = $Advanced.OperatingSystem IPV4Address = $Advanced.IPV4Address IPV6Address = $Advanced.IPV6Address X509NotBefore = $null X509NotAfter = $null AlgorithmIdentifier = $null CipherStrength = $null X509FriendlyName = $null X509SendAsTrustedIssuer = $null X509SerialNumber = $null X509Thumbprint = $null X509SubjectName = $null X509Issuer = $null X509HasPrivateKey = $null X509Version = $null X509Archived = $null Protocol = $null Hash = $null HashStrength = $null KeyExchangeAlgorithm = $null ExchangeStrength = $null ErrorMessage = $null } } else { $Output = [ordered] @{Computer = $ServerName Site = $Advanced.Site IsRO = $Advanced.IsReadOnly IsGC = $Advanced.IsGlobalCatalog GlobalCatalogLDAP = $GlobalCatalogNonSSL.Status GlobalCatalogLDAPS = $GlobalCatalogSSL.Status GlobalCatalogLDAPSBind = $null LDAP = $ConnectionLDAP.Status LDAPS = $ConnectionLDAPS.Status LDAPSBind = $null AvailablePorts = $PortsThatWork -join ',' OperatingSystem = $Advanced.OperatingSystem IPV4Address = $Advanced.IPV4Address IPV6Address = $Advanced.IPV6Address } } if ($VerifyCertificate) { if ($psboundparameters.ContainsKey("Credential")) { $Certificate = Test-LDAPCertificate -Computer $ServerName -Port $PortLDAPS -Credential $Credential $CertificateGC = Test-LDAPCertificate -Computer $ServerName -Port $GCPortLDAPSSL -Credential $Credential } else { $Certificate = Test-LDAPCertificate -Computer $ServerName -Port $PortLDAPS $CertificateGC = Test-LDAPCertificate -Computer $ServerName -Port $GCPortLDAPSSL } $Output['LDAPSBind'] = $Certificate.State $Output['GlobalCatalogLDAPSBind'] = $CertificateGC.State $Output['X509NotBeforeDays'] = $Certificate['X509NotBeforeDays'] $Output['X509NotAfterDays'] = $Certificate['X509NotAfterDays'] $Output['X509DnsNameList'] = $Certificate['X509DnsNameList'] $Output['X509NotBefore'] = $Certificate['X509NotBefore'] $Output['X509NotAfter'] = $Certificate['X509NotAfter'] $Output['AlgorithmIdentifier'] = $Certificate['AlgorithmIdentifier'] $Output['CipherStrength'] = $Certificate['CipherStrength'] $Output['X509FriendlyName'] = $Certificate['X509FriendlyName'] $Output['X509SendAsTrustedIssuer'] = $Certificate['X509SendAsTrustedIssuer'] $Output['X509SerialNumber'] = $Certificate['X509SerialNumber'] $Output['X509Thumbprint'] = $Certificate['X509Thumbprint'] $Output['X509SubjectName'] = $Certificate['X509SubjectName'] $Output['X509Issuer'] = $Certificate['X509Issuer'] $Output['X509HasPrivateKey'] = $Certificate['X509HasPrivateKey'] $Output['X509Version'] = $Certificate['X509Version'] $Output['X509Archived'] = $Certificate['X509Archived'] $Output['Protocol'] = $Certificate['Protocol'] $Output['Hash'] = $Certificate['Hash'] $Output['HashStrength'] = $Certificate['HashStrength'] $Output['KeyExchangeAlgorithm'] = $Certificate['KeyExchangeAlgorithm'] $Output['ExchangeStrength'] = $Certificate['ExchangeStrength'] $Output['ErrorMessage'] = $Certificate['ErrorMessage'] } else { $Output.Remove('LDAPSBind') $Output.Remove('GlobalCatalogLDAPSBind') } if (-not $Advanced) { $Output.Remove('IPV4Address') $Output.Remove('OperatingSystem') $Output.Remove('IPV6Address') $Output.Remove('Site') $Output.Remove('IsRO') $Output.Remove('IsGC') } [PSCustomObject] $Output } 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(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)][Array] $ADObject, [string] $ForestName, [switch] $Extended, [alias('ResolveTypes')][switch] $Resolve, [string] $Principal, [switch] $Inherited, [switch] $NotInherited, [switch] $Bundle, [System.Security.AccessControl.AccessControlType] $AccessControlType, [string[]] $IncludeObjectTypeName, [string[]] $IncludeInheritedObjectTypeName, [string[]] $ExcludeObjectTypeName, [string[]] $ExcludeInheritedObjectTypeName, [System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRights, [System.DirectoryServices.ActiveDirectoryRights[]] $ExcludeActiveDirectoryRights, [System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $IncludeActiveDirectorySecurityInheritance, [System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $ExcludeActiveDirectorySecurityInheritance, [switch] $ADRightsAsArray) Begin { if (-not $Script:ForestGUIDs) { Write-Verbose "Get-ADACL - Gathering Forest GUIDS" $Script:ForestGUIDs = Get-WinADForestGUIDs } } 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.TrimEnd('/') [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 ($AccessControlType) { if ($ACL.AccessControlType -ne $AccessControlType) { continue } } 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 } } $IdentityReference = $ACL.IdentityReference.Value if ($Principal -and $Principal -ne $IdentityReference) { continue } $ReturnObject = [ordered] @{} $ReturnObject['DistinguishedName' ] = $DistinguishedName if ($CanonicalName) { $ReturnObject['CanonicalName'] = $CanonicalName } if ($ObjectClass) { $ReturnObject['ObjectClass'] = $ObjectClass } $ReturnObject['AccessControlType'] = $ACL.AccessControlType $ReturnObject['Principal'] = $IdentityReference if ($Resolve) { $IdentityResolve = Get-WinADObject -Identity $IdentityReference -AddType -Verbose:$false if (-not $IdentityResolve) { $ConvertIdentity = Convert-Identity -Identity $IdentityReference -Verbose:$false $ReturnObject['PrincipalType'] = $ConvertIdentity.Type $ReturnObject['PrincipalObjectType'] = 'foreignSecurityPrincipal' $ReturnObject['PrincipalObjectDomain'] = $ConvertIdentity.DomainName $ReturnObject['PrincipalObjectSid'] = $ConvertIdentity.SID } else { if ($ReturnObject['Principal']) { $ReturnObject['Principal'] = $IdentityResolve.Name } $ReturnObject['PrincipalType'] = $IdentityResolve.Type $ReturnObject['PrincipalObjectType'] = $IdentityResolve.ObjectClass $ReturnObject['PrincipalObjectDomain' ] = $IdentityResolve.DomainName $ReturnObject['PrincipalObjectSid'] = $IdentityResolve.ObjectSID } if (-not $ReturnObject['PrincipalObjectDomain']) { $ReturnObject['PrincipalObjectDomain'] = ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDomainCN } } $ReturnObject['ObjectTypeName'] = $Script:ForestGUIDs["$($ACL.objectType)"] $ReturnObject['InheritedObjectTypeName'] = $Script:ForestGUIDs["$($ACL.inheritedObjectType)"] if ($IncludeObjectTypeName) { if ($IncludeObjectTypeName -notcontains $ReturnObject['ObjectTypeName']) { continue } } if ($IncludeInheritedObjectTypeName) { if ($IncludeInheritedObjectTypeName -notcontains $ReturnObject['InheritedObjectTypeName']) { continue } } if ($ExcludeObjectTypeName) { if ($ExcludeObjectTypeName -contains $ReturnObject['ObjectTypeName']) { continue } } if ($ExcludeInheritedObjectTypeName) { if ($ExcludeInheritedObjectTypeName -contains $ReturnObject['InheritedObjectTypeName']) { continue } } if ($ADRightsAsArray) { $ReturnObject['ActiveDirectoryRights'] = $ADRights } else { $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, [switch] $AddACL, [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 CanonicalName = $CanonicalName ObjectClass = $ObjectClass Owner = $ACLs.Owner } $ErrorMessage = '' } catch { $ACLs = $null $Hash = [ordered] @{DistinguishedName = $DistinguishedName CanonicalName = $CanonicalName ObjectClass = $ObjectClass Owner = $null } $ErrorMessage = $_.Exception.Message } if ($AddACL) { $Hash['ACLs'] = $ACLs } if ($Resolve) { if ($null -eq $Hash.Owner) { $Identity = $null } else { $Identity = Convert-Identity -Identity $Hash.Owner -Verbose:$false } 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-DNSServerIP { [alias('Get-WinDNSServerIP')] param([string[]] $ComputerName, [string[]] $ApprovedList, [pscredential] $Credential) foreach ($Computer in $ComputerName) { $Adapters = Get-CimData -Class Win32_NetworkAdapterConfiguration -ComputerName $Computer -ErrorAction Stop | Where-Object { $_.DHCPEnabled -ne 'True' -and $null -ne $_.DNSServerSearchOrder } if ($Adapters) { foreach ($Adapter in $Adapters) { $AllApproved = $true foreach ($DNS in $Adapter.DNSServerSearchOrder) { if ($DNS -notin $ApprovedList) { $AllApproved = $true } } $AtLeastTwo = $Adapter.DNSServerSearchOrder.Count -ge 2 $Output = [ordered] @{DNSHostName = $Adapter.DNSHostName Status = $AllApproved -and $AtLeastTwo Approved = $AllApproved AtLeastTwo = $AtLeastTwo Connected = $true IPAddress = $Adapter.IPAddress -join ', ' DNSServerSearchOrder = $Adapter.DNSServerSearchOrder -join ', ' DefaultIPGateway = $Adapter.DefaultIPGateway -join ', ' IPSubnet = $Adapter.IPSubnet -join ', ' Description = $Adapter.Description } if (-not $ApprovedList) { $Output.Remove('Approved') $Output.Remove('Status') } [PSCustomObject] $Output } } else { $Output = [ordered] @{DNSHostName = $Computer Status = $false Approved = $false AtLeastTwo = $false Connected = $false IPAddress = $null DNSServerSearchOrder = $null DefaultIPGateway = $null IPSubnet = $null Description = $ErrorMessage } if (-not $ApprovedList) { $Output.Remove('Approved') $Output.Remove('Status') } [PSCustomObject] $Output } } } function Get-WinADACLConfiguration { <# .SYNOPSIS Gets permissions or owners from configuration partition .DESCRIPTION Gets permissions or owners from configuration partition for one or multiple types .PARAMETER ObjectType Gets permissions or owners from one or multiple types (and only that type). Possible choices are sites, subnets, interSiteTransport, siteLink, wellKnownSecurityPrincipals .PARAMETER ContainerType Gets permissions or owners from one or multiple types (including containers and anything below it). Possible choices are sites, subnets, interSiteTransport, siteLink, wellKnownSecurityPrincipals, services .PARAMETER Owner Queries for Owners, instead of permissions .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE Get-WinADACLConfiguration -ObjectType 'interSiteTransport', 'siteLink', 'wellKnownSecurityPrincipals' | Format-Table .EXAMPLE Get-WinADACLConfiguration -ContainerType 'sites' -Owner | Format-Table .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'ObjectType')] param([parameter(ParameterSetName = 'ObjectType', Mandatory)][ValidateSet('site', 'subnet', 'interSiteTransport', 'siteLink', 'wellKnownSecurityPrincipal')][string[]] $ObjectType, [parameter(ParameterSetName = 'FolderType', Mandatory)][ValidateSet('site', 'subnet', 'interSiteTransport', 'siteLink', 'wellKnownSecurityPrincipal', 'service')][string[]] $ContainerType, [switch] $Owner, [string] $Forest, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0] $ForestDN = ConvertTo-DistinguishedName -ToDomain -CanonicalName $ForestInformation.Forest.Name if ($ObjectType) { if ($ObjectType -contains 'site') { $getADObjectSplat = @{Server = $QueryServer LDAPFilter = '(objectClass=site)' SearchBase = "CN=Sites,CN=Configuration,$($($ForestDN))" SearchScope = 'OneLevel' Properties = 'Name', 'CanonicalName', 'DistinguishedName', 'WhenCreated', 'WhenChanged', 'ObjectClass', 'ProtectedFromAccidentalDeletion', 'siteobjectbl', 'gplink', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Site' -Owner:$Owner } if ($ObjectType -contains 'subnet') { $getADObjectSplat = @{Server = $QueryServer LDAPFilter = '(objectClass=subnet)' SearchBase = "CN=Subnets,CN=Sites,CN=Configuration,$($($ForestDN))" SearchScope = 'OneLevel' Properties = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Subnet' -Owner:$Owner } if ($ObjectType -contains 'interSiteTransport') { $getADObjectSplat = @{Server = $QueryServer LDAPFilter = '(objectClass=interSiteTransport)' SearchBase = "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$($($ForestDN))" SearchScope = 'OneLevel' Properties = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'InterSiteTransport' -Owner:$Owner } if ($ObjectType -contains 'siteLink') { $getADObjectSplat = @{Server = $QueryServer LDAPFilter = '(objectClass=siteLink)' SearchBase = "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$($($ForestDN))" SearchScope = 'OneLevel' Properties = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Site' -Owner:$Owner } if ($ObjectType -contains 'wellKnownSecurityPrincipal') { $getADObjectSplat = @{Server = $QueryServer LDAPFilter = '(objectClass=foreignSecurityPrincipal)' SearchBase = "CN=WellKnown Security Principals,CN=Configuration,$($($ForestDN))" SearchScope = 'OneLevel' Properties = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'WellKnownSecurityPrincipals' -Owner:$Owner } } else { if ($ContainerType -contains 'site') { $getADObjectSplat = @{Server = $QueryServer Filter = "*" SearchBase = "CN=Sites,CN=Configuration,$($($ForestDN))" Properties = 'Name', 'CanonicalName', 'DistinguishedName', 'WhenCreated', 'WhenChanged', 'ObjectClass', 'ProtectedFromAccidentalDeletion', 'siteobjectbl', 'gplink', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Site' -FilterOut -Owner:$Owner } if ($ContainerType -contains 'subnet') { $getADObjectSplat = @{Server = $QueryServer Filter = "*" SearchBase = "CN=Subnets,CN=Sites,CN=Configuration,$($($ForestDN))" Properties = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Subnet' -Owner:$Owner } if ($ContainerType -contains 'interSiteTransport') { $getADObjectSplat = @{Server = $QueryServer Filter = '*' SearchBase = "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$($($ForestDN))" Properties = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'InterSiteTransport' -Owner:$Owner } if ($ContainerType -contains 'siteLink') { $getADObjectSplat = @{Server = $QueryServer Filter = '*' SearchBase = "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$($($ForestDN))" Properties = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'Site' -Owner:$Owner } if ($ContainerType -contains 'service') { $getADObjectSplat = @{Server = $QueryServer Filter = '*' SearchBase = "CN=Services,CN=Configuration,$($($ForestDN))" Properties = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'service' -Owner:$Owner } if ($ContainerType -contains 'wellKnownSecurityPrincipal') { $getADObjectSplat = @{Server = $QueryServer Filter = '*' SearchBase = "CN=WellKnown Security Principals,CN=Configuration,$($($ForestDN))" Properties = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description' } Get-ADConfigurationPermission -ADObjectSplat $getADObjectSplat -ObjectType 'WellKnownSecurityPrincipals' -Owner:$Owner } } } function Get-WinADACLForest { <# .SYNOPSIS Gets permissions or owners from forest .DESCRIPTION Gets permissions or owners from forest .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER Owner Queries for Owners, instead of permissions .PARAMETER Separate Returns OrderedDictionary with each top level container being in separate key .EXAMPLE # With split per sheet $FilePath = "$Env:USERPROFILE\Desktop\PermissionsOutputPerSheet.xlsx" $Permissions = Get-WinADACLForest -Verbose -SplitWorkSheets foreach ($Perm in $Permissions.Keys) { $Permissions[$Perm] | ConvertTo-Excel -FilePath $FilePath -ExcelWorkSheetName $Perm -AutoFilter -AutoFit -FreezeTopRowFirstColumn } $Permissions | Format-Table * .EXAMPLE # With owners in one sheet $FilePath = "$Env:USERPROFILE\Desktop\PermissionsOutput.xlsx" $Permissions = Get-WinADACLForest -Verbose $Permissions | ConvertTo-Excel -FilePath $FilePath -ExcelWorkSheetName 'Permissions' -AutoFilter -AutoFit -FreezeTopRowFirstColumn $Permissions | Format-Table * .EXAMPLE # With split per sheet $FilePath = "$Env:USERPROFILE\Desktop\OwnersOutput.xlsx" $Owners = Get-WinADACLForest -Verbose -SplitWorkSheets -Owner foreach ($Owner in $Owners.Keys) { $Owners[$Owner] | ConvertTo-Excel -FilePath $FilePath -ExcelWorkSheetName $Owner -AutoFilter -AutoFit -FreezeTopRowFirstColumn } $Owners | Format-Table * .EXAMPLE # With owners in one sheet $FilePath = "$Env:USERPROFILE\Desktop\OwnersOutput.xlsx" $Owners = Get-WinADACLForest -Verbose -Owner $Owners | ConvertTo-Excel -FilePath $FilePath -ExcelWorkSheetName 'AllOwners' -AutoFilter -AutoFit -FreezeTopRowFirstColumn $Owners | Format-Table * .NOTES General notes #> [cmdletBinding()] param([string] $Forest, [alias('Domain')][string[]] $IncludeDomains, [string[]] $ExcludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $Owner, [switch] $Separate) $ForestTime = Start-TimeLog $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended $Output = [ordered]@{} foreach ($Domain in $ForestInformation.Domains) { Write-Verbose "Get-WinADACLForest - [Start][Domain $Domain]" $DomainTime = Start-TimeLog $Output[$Domain] = [ordered] @{} $Server = $ForestInformation.QueryServers[$Domain].HostName[0] $DomainStructure = @(Get-ADObject -Filter * -Properties canonicalName -SearchScope Base -Server $Server Get-ADObject -Filter * -Properties canonicalName -SearchScope OneLevel -Server $Server) $DomainStructure = $DomainStructure | Sort-Object -Property canonicalName foreach ($Structure in $DomainStructure) { $Time = Start-TimeLog $ObjectName = "[$Domain][$($Structure.CanonicalName)][$($Structure.ObjectClass)][$($Structure.DistinguishedName)]" $ObjectOutputName = "$($Structure.Name)_$($Structure.ObjectClass)".Replace(' ', '').ToLower() Write-Verbose "Get-WinADACLForest - [Start]$ObjectName" if ($Structure.ObjectClass -eq 'organizationalUnit') { $Containers = Get-ADObject -SearchBase $Structure.DistinguishedName -Filter * -Properties canonicalName -Server $Server -SearchScope Subtree | ForEach-Object { foreach ($I in $Ignore) { if ($_.DistinguishedName -notlike $I) { $_ } } } | Sort-Object canonicalName } elseif ($Structure.ObjectClass -eq 'domainDNS') { $Containers = $Structure } elseif ($Structure.ObjectClass -eq 'container') { $Ignore = @( -join ('*CN=Policies,CN=System,', $ForestInformation['DomainsExtended'][$DOmain].DistinguishedName)) $Containers = Get-ADObject -SearchBase $Structure.DistinguishedName -Filter * -Properties canonicalName -Server $Server -SearchScope Subtree | ForEach-Object { foreach ($I in $Ignore) { if ($_.DistinguishedName -notlike $I) { $_ } } } | Sort-Object canonicalName } else { Write-Verbose "Get-WinADACLForest - [Skip ]$ObjectName[ObjectClass not requested]" continue } if ($Owner) { $MYACL = Get-ADACLOwner -ADObject $Containers -Resolve } else { $MYACL = Get-ADACL -ADObject $Containers -NotInherited -ResolveTypes } if ($Separate) { $Output[$Domain][$ObjectOutputName] = $MYACL } else { $MYACL } $EndTime = Stop-TimeLog -Time $Time -Option OneLiner Write-Verbose "Get-WinADACLForest - [End ]$ObjectName[$EndTime]" } $DomainEndTime = Stop-TimeLog -Time $DomainTime -Option OneLiner Write-Verbose "Get-WinADACLForest - [End ][Domain $Domain][$DomainEndTime]" } $ForestEndTime = Stop-TimeLog -Time $ForestTime -Option OneLiner Write-Verbose "Get-WinADACLForest - [End ][Forest][$ForestEndTime]" if ($Separate) { $Output } } 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' 'PrimaryGroupID') } else { $LapsAvailable = $false $Properties = @('Name' 'OperatingSystem' 'OperatingSystemVersion' 'DistinguishedName' 'LastLogonDate' 'PasswordLastSet' 'PrimaryGroupID') } $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 IsDC = if ($_.PrimaryGroupID -in 516, 521) { $true } else { $false } Laps = $Laps LapsExpirationDays = $LapsExpirationDays LapsExpirationTime = $LapsExpirationTime System = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion LastLogonDate = $_.LastLogonDate LastLogonDays = $LastLogonDays PasswordLastSet = $_.PasswordLastSet PasswordLastChangedDays = $PasswordLastChangedDays OrganizationalUnit = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit DistinguishedName = $_.DistinguishedName } } elseif ($BitlockerOnly) { [PSCustomObject] @{Name = $_.Name Enabled = $_.Enabled Domain = $Domain DNSHostName = $_.DNSHostName IsDC = if ($Computer.PrimaryGroupID -in 516, 521) { $true } else { $false } Encrypted = $Encrypted EncryptedTime = $EncryptedTime System = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion LastLogonDate = $_.LastLogonDate LastLogonDays = $LastLogonDays PasswordLastSet = $_.PasswordLastSet PasswordLastChangedDays = $PasswordLastChangedDays OrganizationalUnit = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit DistinguishedName = $_.DistinguishedName } } else { [PSCustomObject] @{Name = $_.Name Enabled = $_.Enabled Domain = $Domain DNSHostName = $_.DNSHostName IsDC = if ($Computer.PrimaryGroupID -in 516, 521) { $true } else { $false } Encrypted = $Encrypted EncryptedTime = $EncryptedTime Laps = $Laps LapsExpirationDays = $LapsExpirationDays LapsExpirationTime = $LapsExpirationTime System = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion LastLogonDate = $_.LastLogonDate LastLogonDays = $LastLogonDays PasswordLastSet = $_.PasswordLastSet PasswordLastChangedDays = $PasswordLastChangedDays OrganizationalUnit = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToOrganizationalUnit DistinguishedName = $_.DistinguishedName } } } } $FormattedComputers } function Get-WinADComputerACLLAPS { <# .SYNOPSIS Gathers information from all computers whether they have ACL to write to LAPS properties or not .DESCRIPTION Gathers information from all computers whether they have ACL to write to LAPS properties or not .PARAMETER ACLMissingOnly Show only computers which do not have ability to write to LAPS properties .EXAMPLE Get-WinADComputerAclLAPS | Format-Table * .EXAMPLE Get-WinADComputerAclLAPS -ACLMissingOnly | Format-Table * .NOTES General notes #> [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $ACLMissingOnly, [System.Collections.IDictionary] $ExtendedForestInformation) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $Computers = Get-ADComputer -Filter * -Properties PrimaryGroupID, LastLogonDate, PasswordLastSet, WhenChanged, OperatingSystem, servicePrincipalName -Server $ForestInformation.QueryServers[$Domain].HostName[0] foreach ($Computer in $Computers) { $ComputerLocation = ($Computer.DistinguishedName -split ',').Replace('OU=', '').Replace('CN=', '').Replace('DC=', '') $Region = $ComputerLocation[-4] $Country = $ComputerLocation[-5] $ACLs = Get-ADACL -ADObject $Computer.DistinguishedName -Principal 'NT AUTHORITY\SELF' $LAPS = $false $LAPSExpirationTime = $false foreach ($ACL in $ACLs) { if ($ACL.ObjectTypeName -eq 'ms-Mcs-AdmPwd') { if ($ACL.AccessControlType -eq 'Allow' -and $ACL.ActiveDirectoryRights -like '*WriteProperty*') { $LAPS = $true } } elseif ($ACL.ObjectTypeName -eq 'ms-Mcs-AdmPwdExpirationTime') { if ($ACL.AccessControlType -eq 'Allow' -and $ACL.ActiveDirectoryRights -like '*WriteProperty*') { $LAPSExpirationTime = $true } } } if ($ACLMissingOnly -and $LAPS -eq $true) { continue } [PSCustomObject] @{Name = $Computer.Name SamAccountName = $Computer.SamAccountName DomainName = $Domain Enabled = $Computer.Enabled IsDC = if ($Computer.PrimaryGroupID -in 516, 521) { $true } else { $false } WhenChanged = $Computer.WhenChanged LapsACL = $LAPS LapsExpirationACL = $LAPSExpirationTime OperatingSystem = $Computer.OperatingSystem Level0 = $Region Level1 = $Country DistinguishedName = $Computer.DistinguishedName LastLogonDate = $Computer.LastLogonDate PasswordLastSet = $Computer.PasswordLastSet ServicePrincipalName = $Computer.servicePrincipalName } } } } function Get-WinADComputers { [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $PerDomain) $Today = Get-Date $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $Output = [ordered] @{} foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $Properties = @('DistinguishedName', 'LastLogonDate', 'PasswordLastSet', 'Enabled', 'DnsHostName', 'PasswordNeverExpires', 'PasswordNotRequired', 'PasswordExpired', 'Manager', 'OperatingSystemVersion', 'OperatingSystem' , 'TrustedForDelegation', 'WhenCreated', 'WhenChanged', 'PrimaryGroupID') $Computers = Get-ADComputer -Filter * -Server $QueryServer -Properties $Properties $Output[$Domain] = foreach ($Computer in $Computers) { $ComputerLocation = ($Computer.DistinguishedName -split ',').Replace('OU=', '').Replace('CN=', '').Replace('DC=', '') $Region = $ComputerLocation[-4] $Country = $ComputerLocation[-5] if ($null -ne $Computer.LastLogonDate) { $LastLogonDays = "$(-$($Computer.LastLogonDate - $Today).Days)" } else { $LastLogonDays = $null } if ($null -ne $Computer.PasswordLastSet) { $PasswordLastChangedDays = "$(-$($Computer.PasswordLastSet - $Today).Days)" } else { $PasswordLastChangedDays = $null } [PSCustomObject] @{Name = $Computer.Name SamAccountName = $Computer.SamAccountName IsDC = if ($Computer.PrimaryGroupID -in 516, 521) { $true } else { $false } WhenChanged = $Computer.WhenChanged Enabled = $Computer.Enabled LastLogonDays = $LastLogonDays PasswordLastDays = $PasswordLastChangedDays Level0 = $Region Level1 = $Country OperatingSystem = $Computer.OperatingSystem OperatingSystemName = ConvertTo-OperatingSystem -OperatingSystem $Computer.OperatingSystem -OperatingSystemVersion $Computer.OperatingSystemVersion DistinguishedName = $Computer.DistinguishedName LastLogonDate = $Computer.LastLogonDate PasswordLastSet = $Computer.PasswordLastSet PasswordNeverExpires = $Computer.PasswordNeverExpires PasswordNotRequired = $Computer.PasswordNotRequired PasswordExpired = $Computer.PasswordExpired ManagerDN = $Computer.Manager Description = $Computer.Description TrustedForDelegation = $Computer.TrustedForDelegation } } } if ($PerDomain) { $Output } else { $Output.Values } } function Get-WinADDelegatedAccounts { [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) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended foreach ($Domain in $ForestInformation.Domains) { $SERVER_TRUST_ACCOUNT = 0x2000 $TRUSTED_FOR_DELEGATION = 0x80000 $TRUSTED_TO_AUTH_FOR_DELEGATION = 0x1000000 $PARTIAL_SECRETS_ACCOUNT = 0x4000000 $bitmask = $TRUSTED_FOR_DELEGATION -bor $TRUSTED_TO_AUTH_FOR_DELEGATION -bor $PARTIAL_SECRETS_ACCOUNT $filter = @" (& (servicePrincipalname=*) (| (msDS-AllowedToActOnBehalfOfOtherIdentity=*) (msDS-AllowedToDelegateTo=*) (UserAccountControl:1.2.840.113556.1.4.804:=$bitmask) ) (| (objectcategory=computer) (objectcategory=person) (objectcategory=msDS-GroupManagedServiceAccount) (objectcategory=msDS-ManagedServiceAccount) ) ) "@ -replace "[\s\n]", '' $PropertyList = @('Enabled' "servicePrincipalname", "useraccountcontrol", "samaccountname", "msDS-AllowedToDelegateTo", "msDS-AllowedToActOnBehalfOfOtherIdentity" 'IsCriticalSystemObject' 'LastLogon' 'PwdLastSet' 'WhenChanged' 'WhenCreated') try { $Accounts = Get-ADObject -LDAPFilter $filter -SearchBase $ForestInformation.DomainsExtended[$Domain].DistinguishedName -SearchScope Subtree -Properties $propertylist -Server $ForestInformation.QueryServers[$Domain].HostName[0] } catch { $Accounts = $null Write-Warning -Message "Get-WinADDelegatedAccounts - Failed to get information: $($_.Exception.Message)" } foreach ($Account in $Accounts) { $UAC = Convert-UserAccountControl -UserAccountControl $Account.useraccountcontrol $IsDC = ($Account.useraccountcontrol -band $SERVER_TRUST_ACCOUNT) -ne 0 $FullDelegation = ($Account.useraccountcontrol -band $TRUSTED_FOR_DELEGATION) -ne 0 $ConstrainedDelegation = ($Account.'msDS-AllowedToDelegateTo').count -gt 0 $IsRODC = ($Account.useraccountcontrol -band $PARTIAL_SECRETS_ACCOUNT) -ne 0 $ResourceDelegation = $null -ne $Account.'msDS-AllowedToActOnBehalfOfOtherIdentity' $PasswordLastSet = [datetime]::FromFileTimeUtc($Account.pwdLastSet) $LastLogonDate = [datetime]::FromFileTimeUtc($Account.LastLogon) [PSCustomobject] @{DomainName = $Domain SamAccountName = $Account.samaccountname Enabled = $UAC -notcontains 'ACCOUNTDISABLE' ObjectClass = $Account.objectclass IsDC = $IsDC IsRODC = $IsRODC FullDelegation = $FullDelegation ConstrainedDelegation = $ConstrainedDelegation ResourceDelegation = $ResourceDelegation LastLogonDate = $LastLogonDate PasswordLastSet = $PasswordLastSet UserAccountControl = $UAC WhenCreated = $Account.WhenCreated WhenChanged = $Account.WhenChanged IsCriticalSystemObject = $Account.IsCriticalSystemObject AllowedToDelagateTo = $Account.'msDS-AllowedToDelegateTo' AllowedToActOnBehalfOfOtherIdentity = $Account.'msDS-AllowedToActOnBehalfOfOtherIdentity' } } } } 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, [switch] $SkipGPO, [switch] $SkipAutodetection, [System.Collections.IDictionary] $ExtendedForestInformation) $Today = (Get-Date) $Yesterday = (Get-Date -Hour 0 -Second 0 -Minute 0 -Millisecond 0).AddDays(-$EventDays) if (-not $SkipAutodetection) { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation -Extended } else { if (-not $IncludeDomains) { Write-Warning "Get-WinADDFSHealth - You need to specify domain when using SkipAutodetection." return } $ForestInformation = @{Domains = $IncludeDomains DomainDomainControllers = @{} } foreach ($Domain in $IncludeDomains) { $ForestInformation['DomainDomainControllers'][$Domain] = [System.Collections.Generic.List[Object]]::new() foreach ($DC in $IncludeDomainControllers) { try { $DCInformation = Get-ADDomainController -Identity $DC -Server $Domain -ErrorAction Stop Add-Member -InputObject $DCInformation -MemberType NoteProperty -Value $DCInformation.ComputerObjectDN -Name 'DistinguishedName' -Force $ForestInformation['DomainDomainControllers'][$Domain].Add($DCInformation) } catch { Write-Warning "Get-WinADDFSHealth - Can't get DC details. Skipping with error: $($_.Exception.Message)" continue } } } } [Array] $Table = foreach ($Domain in $ForestInformation.Domains) { Write-Verbose "Get-WinADDFSHealth - Processing $Domain" [Array] $DomainControllersFull = $ForestInformation['DomainDomainControllers']["$Domain"] if ($DomainControllersFull.Count -eq 0) { continue } if (-not $SkipAutodetection) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] } else { $QueryServer = $DomainControllersFull[0].HostName } if (-not $SkipGPO) { try { $SystemsContainer = $ForestInformation['DomainsExtended'][$Domain].SystemsContainer if ($SystemsContainer) { $PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer) } [Array]$GPOs = Get-ADObject -ErrorAction Stop -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer -Properties Name, gPCFileSysPath, DisplayName, DistinguishedName, Description, Created, Modified, ObjectClass, ObjectGUID } 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.OperationMasterRoles -contains 'PDCEmulator' '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 } if ($SkipGPO) { $DomainSummary.Remove('GroupPolicyOutput') $DomainSummary.Remove('GroupPolicyCount') $DomainSummary.Remove('SYSVOLCount') } $WarningVar = $null $DFSReplicatedFolderInfoAll = Get-CimData -NameSpace "root\microsoftdfs" -Class 'dfsrreplicatedfolderinfo' -ComputerName $Hostname -WarningAction SilentlyContinue -WarningVariable WarningVar -Verbose:$false $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 -Identity $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 -Identity $LocalSettings -Server $QueryServer -ErrorAction Stop $DomainSummary['DFSLocalSetting'] = if ($DFSLocalSetting) { $true } else { $false } } catch { $DomainSummary['DFSLocalSetting'] = $false } try { $DomainSystemVolume = Get-ADObject -Identity $Subscriber -Server $QueryServer -ErrorAction Stop $DomainSummary['DomainSystemVolume'] = if ($DomainSystemVolume) { $true } else { $false } } catch { $DomainSummary['DomainSystemVolume'] = $false } try { $SysVolSubscription = Get-ADObject -Identity $Subscription -Server $QueryServer -ErrorAction Stop $DomainSummary['SYSVOLSubscription'] = if ($SysVolSubscription) { $true } else { $false } } catch { $DomainSummary['SYSVOLSubscription'] = $false } if (-not $SkipGPO) { 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 = @(if (-not $SkipGPO) { $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-WinADDHCP { [cmdletBinding()] param() $ForestDomainControllers = Get-WinADForestControllers try { $DHCPs = Get-DhcpServerInDC -Verbose } catch { Write-Warning -Message "Get-WinADDHCP - Couldn't get DHCP data from AD: $($_.Exception.Message)" return } $CacheDHCP = @{} $CacheAD = [ordered] @{} foreach ($DHCP in $DHCPs) { $CacheDHCP[$DHCP.DNSName] = $DHCP } foreach ($DC in $ForestDomainControllers) { $CacheAD[$DC.HostName] = $DC } foreach ($DHCP in $DHCPs) { $DHCPObject = [ordered] @{DNSName = $DHCP.DNSName IPAddress = $DHCP.IPAddress } if ($CacheAD[$DHCP.DNSName]) { $DHCPObject['IsDC'] = $true $DHCPObject['IsRODC'] = $CacheAD[$DHCP.DNSName].IsReadOnly $DHCPObject['IsGlobalCatalog'] = $CacheAD[$DHCP.DNSName].IsGlobalCatalog $DHCPObject['DCIPv4'] = $CacheAD[$DHCP.DNSName].IPV4Address $DHCPObject['DCIPv6'] = $CacheAD[$DHCP.DNSName].IPV6Address } else { $DHCPObject['IsDC'] = $false $DHCPObject['IsRODC'] = $false $DHCPObject['IsGlobalCatalog'] = $false $DHCPObject['DCIPv4'] = $null $DHCPObject['DCIPv6'] = $null } $DNS = Resolve-DnsName -Name $DHCP.DNSName -ErrorAction SilentlyContinue if ($DNS) { $DHCPObject['IsInDNS'] = $true $DHCPObject['DNSType'] = $DNS.Type } else { $DHCPObject['IsInDNS'] = $false $DHCPObject['DNSType'] = $null } [PSCustomObject] $DHCPObject } } 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-WinADDomain { [cmdletBinding()] param([string] $Domain) try { if ($Domain) { $Type = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain $Context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new($Type, $Domain) $DomainInformation = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($Context) } else { $DomainInformation = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain() } } catch { Write-Warning "Get-WinADDomain - Can't get $Domain information, error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" } $DomainInformation } Function Get-WinADDuplicateObject { [alias('Get-WinADForestObjectsConflict')] [CmdletBinding()] Param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [string] $PartialMatchDistinguishedName, [string[]] $IncludeObjectClass, [string[]] $ExcludeObjectClass, [switch] $Extended, [switch] $NoPostProcessing) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $DC = $ForestInformation['QueryServers']["$Domain"].HostName[0] $getADObjectSplat = @{LDAPFilter = "(|(cn=*\0ACNF:*)(ou=*CNF:*))" Properties = 'DistinguishedName', 'ObjectClass', 'DisplayName', 'SamAccountName', 'Name', 'ObjectCategory', 'WhenCreated', 'WhenChanged', 'ProtectedFromAccidentalDeletion', 'ObjectGUID' Server = $DC SearchScope = 'Subtree' } $Objects = Get-ADObject @getADObjectSplat foreach ($_ in $Objects) { if ($ExcludeObjectClass) { if ($ExcludeObjectClass -contains $_.ObjectClass) { continue } } if ($IncludeObjectClass) { if ($IncludeObjectClass -notcontains $_.ObjectClass) { continue } } if ($PartialMatchDistinguishedName) { if ($_.DistinguishedName -notlike $PartialMatchDistinguishedName) { continue } } if ($NoPostProcessing) { $_ continue } $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToDomainCN $ConflictObject = [ordered] @{ConflictDN = $_.DistinguishedName ConflictWhenChanged = $_.WhenChanged DomainName = $DomainName ObjectClass = $_.ObjectClass } $LiveObjectData = [ordered] @{LiveDn = "N/A" LiveWhenChanged = "N/A" } $RestData = [ordered] @{DisplayName = $_.DisplayName Name = $_.Name.Replace("`n", ' ') SamAccountName = $_.SamAccountName ObjectCategory = $_.ObjectCategory WhenCreated = $_.WhenCreated WhenChanged = $_.WhenChanged ProtectedFromAccidentalDeletion = $_.ProtectedFromAccidentalDeletion ObjectGUID = $_.ObjectGUID.Guid } if ($Extended) { $LiveObject = $null $ConflictObject = $ConflictObject + $LiveObjectData + $RestData 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 } } } else { $ConflictObject = $ConflictObject + $RestData } [PSCustomObject] $ConflictObject } } } function Get-WinADForest { [cmdletBinding()] param([string] $Forest) try { if ($Forest) { $Type = [System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest $Context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new($Type, $Forest) $ForestInformation = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($Context) } else { $ForestInformation = ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()) } } catch { Write-Warning "Get-WinADForest - Can't get $Forest information, error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" } $ForestInformation } function Get-WinADForestControllerInformation { [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation) $Today = Get-Date $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Verbose:$false foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] $Properties = @('PrimaryGroupID' 'PrimaryGroup' 'Enabled' 'ManagedBy' 'OperatingSystem' 'OperatingSystemVersion' 'PasswordLastSet' 'PasswordExpired' 'PasswordNeverExpires' 'PasswordNotRequired' 'TrustedForDelegation' 'UseDESKeyOnly' 'TrustedToAuthForDelegation' 'WhenCreated' 'WhenChanged' 'LastLogonDate' 'IPv4Address' 'IPv6Address') $DCs = Get-ADComputer -Server $QueryServer -SearchBase $ForestInformation['DomainsExtended'][$Domain].DomainControllersContainer -Filter * -Properties $Properties $Count = 0 foreach ($DC in $DCs) { $Count++ Write-Verbose -Message "Get-WinADForestControllerInformation - Processing [$($Domain)]($Count/$($DCs.Count)) $($DC.DNSHostName)" $Owner = Get-ADACLOwner -ADObject $DC.DistinguishedName -Resolve if ($null -ne $DC.LastLogonDate) { [int] $LastLogonDays = "$(-$($DC.LastLogonDate - $Today).Days)" } else { $LastLogonDays = $null } if ($null -ne $DC.PasswordLastSet) { [int] $PasswordLastChangedDays = "$(-$($DC.PasswordLastSet - $Today).Days)" } else { $PasswordLastChangedDays = $null } $DNS = Resolve-DnsName -DnsOnly -Name $DC.DNSHostName -ErrorAction SilentlyContinue -QuickTimeout -Verbose:$false if ($DNS) { $ResolvedIP4 = ($DNS | Where-Object { $_.Type -eq 'A' }).IPAddress $ResolvedIP6 = ($DNS | Where-Object { $_.Type -eq 'AAAA' }).IPAddress $DNSStatus = $true } else { $ResolvedIP4 = $null $ResolvedIP6 = $null $DNSStatus = $false } [PSCustomObject] @{DNSHostName = $DC.DNSHostName DomainName = $Domain Enabled = $DC.Enabled DNSStatus = $DNSStatus IPAddressStatusV4 = if ($ResolvedIP4 -eq $DC.IPv4Address) { $true } else { $false } IPAddressStatusV6 = if ($ResolvedIP6 -eq $DC.IPv6Address) { $true } else { $false } IPAddressHasOneIpV4 = $ResolvedIP4 -isnot [Array] IPAddressHasOneipV6 = $ResolvedIP6 -isnot [Array] ManagerNotSet = $Null -eq $ManagedBy OwnerType = $Owner.OwnerType PasswordLastChangedDays = $PasswordLastChangedDays LastLogonDays = $LastLogonDays Owner = $Owner.OwnerName OwnerSid = $Owner.OwnerSid ManagedBy = $DC.ManagedBy DNSResolvedIPv4 = $ResolvedIP4 DNSResolvedIPv6 = $ResolvedIP6 IPv4Address = $DC.IPv4Address IPv6Address = $DC.IPv6Address LastLogonDate = $DC.LastLogonDate OperatingSystem = $DC.OperatingSystem OperatingSystemVersion = $DC.OperatingSystemVersion PasswordExpired = $DC.PasswordExpired PasswordLastSet = $DC.PasswordLastSet PasswordNeverExpires = $DC.PasswordNeverExpires PasswordNotRequired = $DC.PasswordNotRequired TrustedForDelegation = $DC.TrustedForDelegation TrustedToAuthForDelegation = $DC.TrustedToAuthForDelegation UseDESKeyOnly = $DC.UseDESKeyOnly WhenCreated = $DC.WhenCreated WhenChanged = $DC.WhenChanged DistinguishedName = $DC.DistinguishedName } } } } 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 'Description' = $Site.'Description' 'CanonicalName' = $Site.'CanonicalName' 'Subnets Count' = $Subnets.Count 'Domain Controllers Count' = $DCs.Count 'Location' = $Site.'Location' 'ManagedBy' = $Site.'ManagedBy' 'Subnets' = if ($Splitter) { $Subnets -join $Splitter } else { $Subnets } 'Domain Controllers' = if ($Splitter) { ($DCs).HostName -join $Splitter } else { ($DCs).HostName } 'DistinguishedName' = $Site.'DistinguishedName' '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' 'sDRightsEffective' = $Site.'sDRightsEffective' 'Topology Cleanup Enabled' = $Site.'TopologyCleanupEnabled' 'Topology Detect Stale Enabled' = $Site.'TopologyDetectStaleEnabled' 'Topology Minimum Hops Enabled' = $Site.'TopologyMinimumHopsEnabled' 'Universal Group Caching Enabled' = $Site.'UniversalGroupCachingEnabled' 'Universal Group Caching Refresh Site' = $Site.'UniversalGroupCachingRefreshSite' 'Windows Server 2000 Bridgehead Selection Method Enabled' = $Site.'WindowsServer2000BridgeheadSelectionMethodEnabled' 'Windows Server 2000 KCC ISTG Selection Behavior Enabled' = $Site.'WindowsServer2000KCCISTGSelectionBehaviorEnabled' 'Windows Server 2003 KCC Behavior Enabled' = $Site.'WindowsServer2003KCCBehaviorEnabled' 'Windows Server 2003 KCC Ignore Schedule Enabled' = $Site.'WindowsServer2003KCCIgnoreScheduleEnabled' 'Windows Server 2003 KCC SiteLink Bridging Enabled' = $Site.'WindowsServer2003KCCSiteLinkBridgingEnabled' 'Created' = $Site.Created 'Modified' = $Site.Modified 'Deleted' = $Site.Deleted } } else { [PSCustomObject] @{'Name' = $Site.Name 'Description' = $Site.'Description' 'CanonicalName' = $Site.'CanonicalName' 'SubnetsCount' = $Subnets.Count 'DomainControllersCount' = $DCs.Count 'Subnets' = if ($Splitter) { $Subnets -join $Splitter } else { $Subnets } 'DomainControllers' = if ($Splitter) { ($DCs).HostName -join $Splitter } else { ($DCs).HostName } 'Location' = $Site.'Location' 'ManagedBy' = $Site.'ManagedBy' 'DistinguishedName' = $Site.'DistinguishedName' 'ProtectedFromAccidentalDeletion' = $Site.'ProtectedFromAccidentalDeletion' 'RedundantServerTopologyEnabled' = $Site.'RedundantServerTopologyEnabled' 'AutomaticInterSiteTopologyGenerationEnabled' = $Site.'AutomaticInterSiteTopologyGenerationEnabled' 'AutomaticTopologyGenerationEnabled' = $Site.'AutomaticTopologyGenerationEnabled' 'sDRightsEffective' = $Site.'sDRightsEffective' 'TopologyCleanupEnabled' = $Site.'TopologyCleanupEnabled' 'TopologyDetectStaleEnabled' = $Site.'TopologyDetectStaleEnabled' 'TopologyMinimumHopsEnabled' = $Site.'TopologyMinimumHopsEnabled' 'UniversalGroupCachingEnabled' = $Site.'UniversalGroupCachingEnabled' 'UniversalGroupCachingRefreshSite' = $Site.'UniversalGroupCachingRefreshSite' 'WindowsServer2000BridgeheadSelectionMethodEnabled' = $Site.'WindowsServer2000BridgeheadSelectionMethodEnabled' 'WindowsServer2000KCCISTGSelectionBehaviorEnabled' = $Site.'WindowsServer2000KCCISTGSelectionBehaviorEnabled' 'WindowsServer2003KCCBehaviorEnabled' = $Site.'WindowsServer2003KCCBehaviorEnabled' 'WindowsServer2003KCCIgnoreScheduleEnabled' = $Site.'WindowsServer2003KCCIgnoreScheduleEnabled' 'WindowsServer2003KCCSiteLinkBridgingEnabled' = $Site.'WindowsServer2003KCCSiteLinkBridgingEnabled' 'Created' = $Site.Created 'Modified' = $Site.Modified 'Deleted' = $Site.Deleted } } } } function Get-WinADForestSubnet { [alias('Get-WinADSubnet')] [cmdletBinding()] param([string] $Forest, [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $VerifyOverlap) $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0] $ForestDN = ConvertTo-DistinguishedName -ToDomain -CanonicalName $ForestInformation.Forest.Name $ADObjectSplat = @{Server = $QueryServer LDAPFilter = '(objectClass=subnet)' SearchBase = "CN=Subnets,CN=Sites,CN=Configuration,$($($ForestDN))" SearchScope = 'OneLevel' Properties = 'Name', 'distinguishedName', 'CanonicalName', 'WhenCreated', 'whenchanged', 'ProtectedFromAccidentalDeletion', 'siteObject', 'location', 'objectClass', 'Description' } try { $SubnetsList = Get-ADObject @ADObjectSplat -ErrorAction Stop } catch { Write-Warning "Get-WinADSites - LDAP Filter: $($ADObjectSplat.LDAPFilter), SearchBase: $($ADObjectSplat.SearchBase)), Error: $($_.Exception.Message)" } $Cache = @{} if ($VerifyOverlap) { $Subnets = Get-ADSubnet -Subnets $SubnetsList -AsHashTable $OverlappingSubnets = Test-ADSubnet -Subnets $Subnets foreach ($Subnet in $OverlappingSubnets) { if (-not $Cache[$Subnet.Name]) { $Cache[$Subnet.Name] = [System.Collections.Generic.List[string]]::new() } $Cache[$Subnet.Name].Add($Subnet.OverlappingSubnet) } foreach ($Subnet in $Subnets) { if ($Subnet.Type -eq 'IPv4') { $Subnet['Overlap'] = $false } if ($Cache[$Subnet.Name]) { $Subnet['Overlap'] = $true $Subnet['OverLapList'] = $Cache[$Subnet.Name] } else {} [PSCustomObject] $Subnet } } else { Get-ADSubnet -Subnets $Subnets } } function Get-WinADGroupMember { <# .SYNOPSIS The Get-WinADGroupMember cmdlet gets the members of an Active Directory group. Members can be users, groups, and computers. .DESCRIPTION The Get-WinADGroupMember cmdlet gets the members of an Active Directory group. Members can be users, groups, and computers. The Identity parameter specifies the Active Directory group to access. You can identify a group by its distinguished name, GUID, security identifier, or Security Account Manager (SAM) account name. You can also specify the group by passing a group object through the pipeline. For example, you can use the Get-ADGroup cmdlet to get a group object and then pass the object through the pipeline to the Get-WinADGroupMember cmdlet. .PARAMETER Identity Specifies an Active Directory group object .PARAMETER AddSelf Adds details about initial group name to output .PARAMETER SelfOnly Returns only one object that's summary for the whole group .PARAMETER AdditionalStatistics Adds additional data to Self object (when AddSelf is used). This data is available always if SelfOnly is used. It includes count for NestingMax, NestingGroup, NestingGroupSecurity, NestingGroupDistribution. It allows for easy filtering where we expect security groups only when there are nested distribution groups. .PARAMETER All Adds details about groups, and their nesting. Without this parameter only unique users and computers are returned .EXAMPLE Get-WinADGroupMember -Identity 'EVOTECPL\Domain Admins' -All .EXAMPLE Get-WinADGroupMember -Group 'GDS-TestGroup9' -All -SelfOnly | Format-List * .EXAMPLE Get-WinADGroupMember -Group 'GDS-TestGroup9' | Format-Table * .EXAMPLE Get-WinADGroupMember -Group 'GDS-TestGroup9' -All -AddSelf | Format-Table * .EXAMPLE Get-WinADGroupMember -Group 'GDS-TestGroup9' -All -AddSelf -AdditionalStatistics | Format-Table * .NOTES General notes #> [cmdletBinding()] param([alias('GroupName', 'Group')][Parameter(ValuefromPipeline, Mandatory)][Array] $Identity, [switch] $AddSelf, [switch] $All, [switch] $ClearCache, [switch] $AdditionalStatistics, [switch] $SelfOnly, [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 { $Properties = 'GroupName', 'Name', 'SamAccountName', 'DisplayName', 'Enabled', 'Type', 'Nesting', 'CrossForest', 'ParentGroup', 'ParentGroupDomain', 'GroupDomainName', 'DistinguishedName', 'Sid' if (-not $Script:WinADGroupMemberCache -or $ClearCache) { $Script:WinADGroupMemberCache = @{} $Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() $Script:WinADForestCache = @{Forest = $Forest Domains = $Forest.Domains.Name } } if ($Nesting -eq -1) { $MembersCache = [ordered] @{} } } Process { [Array] $Output = foreach ($GroupName in $Identity) { if (-not $Nested.IsPresent) { $InitialGroup = [ordered] @{GroupName = $GroupName Name = $null SamAccountName = $null DomainName = $null DisplayName = $null Enabled = $null GroupType = $null GroupScope = $null Type = 'group' DirectMembers = 0 DirectGroups = 0 IndirectMembers = 0 TotalMembers = 0 Nesting = $Nesting CircularDirect = $false CircularIndirect = $false CrossForest = $false ParentGroup = '' ParentGroupDomain = '' GroupDomainName = $null DistinguishedName = $null Sid = $null } $CollectedGroups = [System.Collections.Generic.List[string]]::new() $Nesting = -1 } $Nesting++ $ADGroupName = Get-WinADObject -Identity $GroupName -IncludeGroupMembership if ($ADGroupName) { if (-not $Nested.IsPresent) { $InitialGroup.GroupName = $ADGroupName.Name $InitialGroup.DomainName = $ADGroupName.DomainName if ($AddSelf -or $SelfOnly) { $InitialGroup.Name = $ADGroupName.Name $InitialGroup.SamAccountName = $ADGroupName.SamAccountName $InitialGroup.DisplayName = $ADGroupName.DisplayName $InitialGroup.GroupDomainName = $ADGroupName.DomainName $InitialGroup.DistinguishedName = $ADGroupName.DistinguishedName $InitialGroup.Sid = $ADGroupName.ObjectSID $InitialGroup.GroupType = $ADGroupName.GroupType $InitialGroup.GroupScope = $ADGroupName.GroupScope } } $Script:WinADGroupMemberCache[$ADGroupName.DistinguishedName] = $ADGroupName if ($Circular -or $CollectedGroups -contains $ADGroupName.DistinguishedName) { [Array] $NestedMembers = foreach ($MyIdentity in $ADGroupName.Members) { if ($Script:WinADGroupMemberCache[$MyIdentity]) { $Script:WinADGroupMemberCache[$MyIdentity] } else { $ADObject = Get-WinADObject -Identity $MyIdentity -IncludeGroupMembership $Script:WinADGroupMemberCache[$MyIdentity] = $ADObject $Script:WinADGroupMemberCache[$MyIdentity] } } [Array] $NestedMembers = foreach ($Member in $NestedMembers) { if ($CollectedGroups -notcontains $Member.DistinguishedName) { $Member } } $Circular = $null } else { [Array] $NestedMembers = foreach ($MyIdentity in $ADGroupName.Members) { if ($Script:WinADGroupMemberCache[$MyIdentity]) { $Script:WinADGroupMemberCache[$MyIdentity] } else { $ADObject = Get-WinADObject -Identity $MyIdentity -IncludeGroupMembership $Script:WinADGroupMemberCache[$MyIdentity] = $ADObject $Script:WinADGroupMemberCache[$MyIdentity] } } } 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 GroupType = $NestedMember.GroupType GroupScope = $NestedMember.GroupScope Type = $NestedMember.ObjectClass DirectMembers = 0 DirectGroups = 0 IndirectMembers = 0 TotalMembers = 0 Nesting = $Nesting CircularDirect = $false CircularIndirect = $false CrossForest = $false ParentGroup = $ADGroupName.name ParentGroupDomain = $DomainParentGroup GroupDomainName = $InitialGroup.DomainName DistinguishedName = $NestedMember.DistinguishedName Sid = $NestedMember.ObjectSID } if ($NestedMember.DomainName -notin $Script:WinADForestCache['Domains']) { $CreatedObject['CrossForest'] = $true } if ($NestedMember.ObjectClass -eq "group") { if ($ADGroupName.memberof -contains $NestedMember.DistinguishedName) { $Circular = $ADGroupName.DistinguishedName $CreatedObject['CircularDirect'] = $true } $CollectedGroups.Add($ADGroupName.DistinguishedName) if ($CollectedGroups -contains $NestedMember.DistinguishedName) { $CreatedObject['CircularIndirect'] = $true } if ($All) { [PSCustomObject] $CreatedObject } Write-Verbose "Get-WinADGroupMember - Going into $($NestedMember.DistinguishedName) (Nesting: $Nesting) (Circular:$Circular)" $OutputFromGroup = Get-WinADGroupMember -GroupName $NestedMember -Nesting $Nesting -Circular $Circular -InitialGroup $InitialGroup -CollectedGroups $CollectedGroups -Nested -All:$All.IsPresent $OutputFromGroup 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 | Select-Object -Property $Properties } else { if ($AddSelf -or $SelfOnly) { $InitialGroup.DirectMembers = $MembersCache[$InitialGroup.DistinguishedName].DirectMembersCount $InitialGroup.DirectGroups = $MembersCache[$InitialGroup.DistinguishedName].DirectGroupsCount foreach ($Group in $MembersCache[$InitialGroup.DistinguishedName].DirectGroups) { $InitialGroup.IndirectMembers = $MembersCache[$Group.DistinguishedName].DirectMembersCount + $InitialGroup.IndirectMembers } $AllMembersForGivenGroup = @(foreach ($DirectGroup in $MembersCache[$InitialGroup.DistinguishedName].DirectGroups) { $MembersCache[$DirectGroup.DistinguishedName].DirectMembers } $MembersCache[$InitialGroup.DistinguishedName].DirectMembers $MembersCache[$InitialGroup.DistinguishedName].IndirectMembers) $InitialGroup['TotalMembers'] = @($AllMembersForGivenGroup | Sort-Object -Unique -Property DistinguishedName).Count if ($AdditionalStatistics -or $SelfOnly) { $InitialGroup['NestingMax'] = ($Output.Nesting | Sort-Object -Unique -Descending)[0] $NestingObjectTypes = $Output.Where( { $_.Type -eq 'group' }, 'split') $NestingGroupTypes = $NestingObjectTypes[0].Where( { $_.GroupType -eq 'Security' }, 'split') $InitialGroup['NestingGroup'] = ($NestingObjectTypes[0]).Count $InitialGroup['NestingGroupSecurity'] = ($NestingGroupTypes[0]).Count $InitialGroup['NestingGroupDistribution'] = ($NestingGroupTypes[1]).Count } [PSCustomObject] $InitialGroup } if (-not $SelfOnly) { foreach ($Object in $Output) { if ($Object.Type -eq 'group') { $Object.DirectMembers = $MembersCache[$Object.DistinguishedName].DirectMembersCount $Object.DirectGroups = $MembersCache[$Object.DistinguishedName].DirectGroupsCount foreach ($DirectGroup in $MembersCache[$Object.DistinguishedName].DirectGroups) { $Object.IndirectMembers = $MembersCache[$DirectGroup.DistinguishedName].DirectMembersCount + $Object.IndirectMembers } $AllMembersForGivenGroup = @(foreach ($DirectGroup in $MembersCache[$Object.DistinguishedName].DirectGroups) { $MembersCache[$DirectGroup.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-WinADGroupMemberOf { [cmdletBinding()] param([parameter(Position = 0, Mandatory)][Array] $Identity, [switch] $AddSelf, [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] $InitialObject, [Parameter(DontShow)][switch] $Nested) Begin { if (-not $Script:WinADGroupObjectCache -or $ClearCache) { $Script:WinADGroupObjectCache = @{} } } Process { [Array] $Output = foreach ($MyObject in $Identity) { $Object = Get-WinADObject -Identity $MyObject Write-Verbose "Get-WinADGroupMemberOf - starting $($Object.Name)/$($Object.DomainName)" if (-not $Nested.IsPresent) { $InitialObject = [ordered] @{ObjectName = $Object.Name ObjectSamAccountName = $Object.SamAccountName Name = $Object.Name SamAccountName = $Object.SamAccountName DomainName = $Object.DomainName DisplayName = $Object.DisplayName Enabled = $Object.Enabled Type = $Object.ObjectClass GroupType = $Object.GroupType GroupScope = $Object.GroupScope Nesting = $Nesting Circular = $false ParentGroup = '' ParentGroupDomain = '' ObjectDomainName = $Object.DomainName DistinguishedName = $Object.Distinguishedname Sid = $Object.ObjectSID } $CollectedGroups = [System.Collections.Generic.List[string]]::new() $Nesting = -1 } $Nesting++ if ($Object) { $Script:WinADGroupObjectCache[$Object.DistinguishedName] = $Object if ($Circular) { [Array] $NestedMembers = foreach ($MyIdentity in $Object.MemberOf) { if ($Script:WinADGroupObjectCache[$MyIdentity]) { $Script:WinADGroupObjectCache[$MyIdentity] } else { Write-Verbose "Get-WinADGroupMemberOf - Requesting more data on $MyIdentity (Circular: $true)" $ADObject = Get-WinADObject -Identity $MyIdentity $Script:WinADGroupObjectCache[$MyIdentity] = $ADObject $Script:WinADGroupObjectCache[$MyIdentity] } } [Array] $NestedMembers = foreach ($Member in $NestedMembers) { if ($CollectedGroups -notcontains $Member.DistinguishedName) { $Member } } $Circular = $null } else { [Array] $NestedMembers = foreach ($MyIdentity in $Object.MemberOf) { if ($Script:WinADGroupObjectCache[$MyIdentity]) { $Script:WinADGroupObjectCache[$MyIdentity] } else { Write-Verbose "Get-WinADGroupMemberOf - Requesting more data on $MyIdentity (Circular: $false)" $ADObject = Get-WinADObject -Identity $MyIdentity $Script:WinADGroupObjectCache[$MyIdentity] = $ADObject $Script:WinADGroupObjectCache[$MyIdentity] } } } foreach ($NestedMember in $NestedMembers) { Write-Verbose "Get-WinADGroupMemberOf - processing $($InitialObject.ObjectName) nested member $($NestedMember.name)" $CreatedObject = [ordered] @{ObjectName = $InitialObject.ObjectName ObjectSamAccountName = $InitialObject.SamAccountName Name = $NestedMember.name SamAccountName = $NestedMember.SamAccountName DomainName = $NestedMember.DomainName DisplayName = $NestedMember.DisplayName Enabled = $NestedMember.Enabled Type = $NestedMember.ObjectClass GroupType = $NestedMember.GroupType GroupScope = $NestedMember.GroupScope Nesting = $Nesting Circular = $false ParentGroup = $Object.name ParentGroupDomain = $Object.DomainName ObjectDomainName = $InitialObject.DomainName DistinguishedName = $NestedMember.DistinguishedName Sid = $NestedMember.ObjectSID } if ($NestedMember.ObjectClass -eq "group") { if ($Object.members -contains $NestedMember.DistinguishedName) { $Circular = $Object.DistinguishedName $CreatedObject['Circular'] = $true } $CollectedGroups.Add($Object.DistinguishedName) [PSCustomObject] $CreatedObject Write-Verbose "Get-WinADGroupMemberOf - Going deeper with $($NestedMember.name)" $OutputFromGroup = Get-WinADGroupMemberOf -Identity $NestedMember -Nesting $Nesting -Circular $Circular -InitialObject $InitialObject -CollectedGroups $CollectedGroups -Nested $OutputFromGroup } else { [PSCustomObject] $CreatedObject } } } } } End { if ($Output.Count -gt 0) { if ($Nesting -eq 0) { if ($AddSelf) { [PSCustomObject] $InitialObject } foreach ($MyObject in $Output) { $MyObject } } 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 { <# .SYNOPSIS Gets Active Directory Object .DESCRIPTION Returns Active Directory Object (Computers, Groups, Users or ForeignSecurityPrincipal) using ADSI .PARAMETER Identity Identity of an object. It can be SamAccountName, SID, DistinguishedName or multiple other options .PARAMETER DomainName Choose domain name the objects resides in. This is optional for most objects .PARAMETER Credential Parameter description .PARAMETER IncludeGroupMembership Queries for group members when object is a group .PARAMETER IncludeAllTypes Allows functions to return all objects types and not only Computers, Groups, Users or ForeignSecurityPrincipal .EXAMPLE An example .NOTES General notes #> [cmdletBinding()] param([Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][Array] $Identity, [string] $DomainName, [pscredential] $Credential, [switch] $IncludeGroupMembership, [switch] $IncludeAllTypes, [switch] $AddType, [switch] $Cache, [string[]] $Properties) Begin { if ($Cache -and -not $Script:CacheObjectsWinADObject) { $Script:CacheObjectsWinADObject = @{} } Add-Type -AssemblyName System.DirectoryServices.AccountManagement $GroupTypes = @{'2' = @{Name = 'Distribution Group - Global' Type = 'Distribution' Scope = 'Global' } '4' = @{Name = 'Distribution Group - Domain Local' Type = 'Distribution' Scope = 'Domain local' } '8' = @{Name = 'Distribution Group - Universal' Type = 'Distribution' Scope = 'Universal' } '-2147483640' = @{Name = 'Security Group - Universal' Type = 'Security' Scope = 'Universal' } '-2147483643' = @{Name = 'Security Group - Builtin Local' Type = 'Security' Scope = 'Builtin local' } '-2147483644' = @{Name = 'Security Group - Domain Local' Type = 'Security' Scope = 'Domain local' } '-2147483646' = @{Name = 'Security Group - Global' Type = 'Security' Scope = 'Global' } } } process { foreach ($Ident in $Identity) { $ResolvedIdentity = $null if ($Ident.DistinguishedName) { $Ident = $Ident.DistinguishedName } $TemporaryName = $Ident $TemporaryDomainName = $DomainName if ($Cache -and $Script:CacheObjectsWinADObject[$TemporaryName]) { Write-Verbose "Get-WinADObject - Requesting $TemporaryName from Cache" $Script:CacheObjectsWinADObject[$TemporaryName] continue } if (-not $TemporaryDomainName) { $MatchRegex = [Regex]::Matches($Ident, "S-\d-\d+-(\d+-|){1,14}\d+") if ($MatchRegex.Success) { $ResolvedIdentity = ConvertFrom-SID -SID $MatchRegex.Value $TemporaryDomainName = $ResolvedIdentity.DomainName $Ident = $MatchRegex.Value } elseif ($Ident -like '*\*') { $ResolvedIdentity = Convert-Identity -Identity $Ident if ($ResolvedIdentity.SID) { $TemporaryDomainName = $ResolvedIdentity.DomainName $Ident = $ResolvedIdentity.SID } else { $NetbiosConversion = ConvertFrom-NetbiosName -Identity $Ident if ($NetbiosConversion.DomainName) { $TemporaryDomainName = $NetbiosConversion.DomainName $Ident = $NetbiosConversion.Name } } } elseif ($Ident -like '*@*') { $CNConversion = $Ident -split '@', 2 $TemporaryDomainName = $CNConversion[1] $Ident = $CNConversion[0] } elseif ($Ident -like '*DC=*') { $DNConversion = ConvertFrom-DistinguishedName -DistinguishedName $Ident -ToDomainCN $TemporaryDomainName = $DNConversion } elseif ($Ident -like '*.*') { $ResolvedIdentity = Convert-Identity -Identity $Ident if ($ResolvedIdentity.SID) { $TemporaryDomainName = $ResolvedIdentity.DomainName $Ident = $ResolvedIdentity.SID } else { $CNConversion = $Ident -split '\.', 2 $Ident = $CNConversion[0] $TemporaryDomainName = $CNConversion[1] } } } $Search = [System.DirectoryServices.DirectorySearcher]::new() if ($TemporaryDomainName) { try { $Context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain', $TemporaryDomainName) } catch { Write-Warning "Get-WinADObject - Building context failed ($TemporaryDomainName), error: $($_.Exception.Message)" } } else { try { $Context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain') } catch { Write-Warning "Get-WinADObject - Building context failed, error: $($_.Exception.Message)" } } Try { $IdentityGUID = "" ([System.Guid]$Ident).ToByteArray() | ForEach-Object { $IdentityGUID += $("\{0:x2}" -f $_) } } Catch { $IdentityGUID = "null" } $Search.filter = "(|(DistinguishedName=$Ident)(Name=$Ident)(SamAccountName=$Ident)(UserPrincipalName=$Ident)(objectGUID=$IdentityGUID)(objectSid=$Ident))" if ($TemporaryDomainName) { $Search.SearchRoot = "LDAP://$TemporaryDomainName" } if ($PSBoundParameters['Credential']) { $Cred = [System.DirectoryServices.DirectoryEntry]::new("LDAP://$TemporaryDomainName", $($Credential.UserName), $($Credential.GetNetworkCredential().password)) $Search.SearchRoot = $Cred } Write-Verbose "Get-WinADObject - Requesting $Ident ($TemporaryDomainName)" try { $SearchResults = $($Search.FindAll()) } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "Get-WinADObject - Requesting $Ident ($TemporaryDomainName) failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" } else { Write-Warning "Get-WinADObject - Requesting $Ident ($TemporaryDomainName) failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" continue } } if ($SearchResults.Count -lt 1) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "Requesting $Ident ($TemporaryDomainName) failed with no results." } } foreach ($Object in $SearchResults) { $UAC = Convert-UserAccountControl -UserAccountControl ($Object.properties.useraccountcontrol -as [string]) $ObjectClass = ($Object.properties.objectclass -as [array])[-1] if ($ObjectClass -notin 'group', 'computer', 'user', 'foreignSecurityPrincipal' -and (-not $IncludeAllTypes)) { Write-Warning "Get-WinADObject - Unsupported object ($Ident) of type $ObjectClass. Only user,computer,group and foreignSecurityPrincipal is supported." continue } $Members = $Object.properties.member -as [array] if ($ObjectClass -eq 'group') { if ($IncludeGroupMembership) { $GroupMembers = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context, $Ident).Members [Array] $Members = foreach ($Member in $GroupMembers) { if ($Member.DistinguishedName) { $Member.DistinguishedName } elseif ($Member.DisplayName) { $Member.DisplayName } else { $Member.Sid } } } } $ObjectDomainName = ConvertFrom-DistinguishedName -DistinguishedName ($Object.properties.distinguishedname -as [string]) -ToDomainCN $DisplayName = $Object.properties.displayname -as [string] $SamAccountName = $Object.properties.samaccountname -as [string] $Name = $Object.properties.name -as [string] if ($ObjectClass -eq 'foreignSecurityPrincipal' -and $DisplayName -eq '') { $DisplayName = $ResolvedIdentity.Name if ($DisplayName -like '*\*') { $NetbiosWithName = $DisplayName -split '\\' if ($NetbiosWithName.Count -eq 2) { $NetbiosUser = $NetbiosWithName[1] $Name = $NetbiosUser $SamAccountName = $NetbiosUser } else { $Name = $DisplayName } } else { $Name = $DisplayName } } $GroupType = $Object.properties.grouptype -as [string] if ($Object.Properties.objectsid) { try { $ObjectSID = [System.Security.Principal.SecurityIdentifier]::new($Object.Properties.objectsid[0], 0).Value } catch { Write-Warning "Get-WinADObject - Getting objectsid failed, error: $($_.Exception.Message)" $ObjectSID = $null } } else { $ObjectSID = $null } $ReturnObject = [ordered] @{DisplayName = $DisplayName Name = $Name SamAccountName = $SamAccountName ObjectClass = $ObjectClass Enabled = if ($ObjectClass -eq 'group') { $null } else { $UAC -notcontains 'ACCOUNTDISABLE' } PasswordNeverExpire = if ($ObjectClass -eq 'group') { $null } else { $UAC -contains 'DONT_EXPIRE_PASSWORD' } DomainName = $ObjectDomainName Distinguishedname = $Object.properties.distinguishedname -as [string] WhenCreated = $Object.properties.whencreated -as [string] WhenChanged = $Object.properties.whenchanged -as [string] UserPrincipalName = $Object.properties.userprincipalname -as [string] ObjectSID = $ObjectSID MemberOf = $Object.properties.memberof -as [array] Members = $Members DirectReports = $Object.Properties.directreports GroupScopedType = $GroupTypes[$GroupType].Name GroupScope = $GroupTypes[$GroupType].Scope GroupType = $GroupTypes[$GroupType].Type Description = $Object.properties.description -as [string] } if ($Properties -contains 'LastLogonDate') { $LastLogon = [int64] $Object.properties.lastlogontimestamp[0] if ($LastLogon -ne 9223372036854775807) { $ReturnObject['LastLogonDate'] = [datetime]::FromFileTimeUtc($LastLogon) } else { $ReturnObject['LastLogonDate'] = $null } } if ($Properties -contains 'PasswordLastSet') { $PasswordLastSet = [int64] $Object.properties.pwdlastset[0] if ($PasswordLastSet -ne 9223372036854775807) { $ReturnObject['PasswordLastSet'] = [datetime]::FromFileTimeUtc($PasswordLastSet) } else { $ReturnObject['PasswordLastSet'] = $null } } if ($Properties -contains 'AccountExpirationDate') { $ExpirationDate = [int64] $Object.properties.accountexpires[0] if ($ExpirationDate -ne 9223372036854775807) { $ReturnObject['AccountExpirationDate'] = [datetime]::FromFileTimeUtc($ExpirationDate) } else { $ReturnObject['AccountExpirationDate'] = $null } } if ($AddType) { if (-not $ResolvedIdentity) { $ResolvedIdentity = ConvertFrom-SID -SID $ReturnObject['ObjectSID'] } $ReturnObject['Type'] = $ResolvedIdentity.Type } if ($ReturnObject['Type'] -eq 'WellKnownAdministrative') { if (-not $TemporaryDomainName) { $ReturnObject['DomainName'] = '' } } if ($Cache) { $Script:CacheObjectsWinADObject[$TemporaryName] = [PSCustomObject] $ReturnObject $Script:CacheObjectsWinADObject[$TemporaryName] } else { [PSCustomObject] $ReturnObject } } } } } 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 } $CacheCritical = @{} foreach ($Group in $CriticalGroups) { $Members = Get-WinADGroupMember -Identity $Group.distinguishedname -Verbose:$false foreach ($Member in $Members) { if (-not $CacheCritical[$Member.DistinguishedName]) { $CacheCritical[$Member.DistinguishedName] = [System.Collections.Generic.List[string]]::new() } $CacheCritical[$Member.DistinguishedName].Add($Group.DistinguishedName) } } $AdminCountAll = foreach ($object in $UsersWithAdminCount) { $DistinguishedName = $object.distinguishedname [Array] $IsMemberGroups = foreach ($Group in $CriticalGroups) { $CacheCritical[$DistinguishedName] -contains $Group.DistinguishedName } $IsMember = $IsMemberGroups -contains $true $GroupDomains = $CacheCritical[$DistinguishedName] $IsOrphaned = -not $Object.isCriticalSystemObject -and -not $IsMember if ($Formatted) { $GroupDomains = $GroupDomains -join $Splitter $User = [PSCustomObject] @{DistinguishedName = $Object.DistinguishedName Domain = $Object.domain IsOrphaned = $IsOrphaned IsMember = $IsMember 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' = $IsOrphaned 'IsMember' = $IsMember '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-WinADServiceAccount { [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $PerDomain) $Today = Get-Date $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $Output = [ordered] @{} foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $Properties = @('Name', 'ObjectClass', 'PasswordLastSet', 'PasswordNeverExpires', 'PasswordNotRequired', 'UserPrincipalName', 'SamAccountName', 'LastLogonDate' 'AccountExpirationDate', 'AccountNotDelegated', 'CanonicalName', 'WhenCreated', 'WhenChanged', 'DistinguishedName', 'Enabled', 'Description' 'msDS-HostServiceAccountBL', 'msDS-SupportedEncryptionTypes', 'msDS-User-Account-Control-Computed', 'TrustedForDelegation', 'TrustedToAuthForDelegation' 'msDS-AuthenticatedAtDC', 'msDS-AllowedToActOnBehalfOfOtherIdentity', 'msDS-AllowedToDelegateTo', 'PrincipalsAllowedToRetrieveManagedPassword', 'PrincipalsAllowedToDelegateToAccount' 'msDS-ManagedPasswordInterval', 'msDS-GroupMSAMembership', 'ManagedPasswordIntervalInDays', 'msDS-RevealedDSAs', 'servicePrincipalName') $Accounts = Get-ADServiceAccount -Filter * -Server $QueryServer -Properties $Properties $Output[$Domain] = foreach ($Account in $Accounts) { if ($null -ne $Account.LastLogonDate) { [int] $LastLogonDays = "$(-$($Account.LastLogonDate - $Today).Days)" } else { $LastLogonDays = $null } if ($null -ne $Account.PasswordLastSet) { [int] $PasswordLastChangedDays = "$(-$($Account.PasswordLastSet - $Today).Days)" } else { $PasswordLastChangedDays = $null } [PSCUstomObject] @{Name = $Account.Name Enabled = $Account.Enabled ObjectClass = $Account.ObjectClass CanonicalName = $Account.CanonicalName DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Account.DistinguishedName Description = $Account.Description PasswordLastChangedDays = $PasswordLastChangedDays LastLogonDays = $LastLogonDays 'ManagedPasswordIntervalInDays' = $Account.'ManagedPasswordIntervalInDays' 'msDS-AllowedToDelegateTo' = $Account.'msDS-AllowedToDelegateTo' 'msDS-HostServiceAccountBL' = $Account.'msDS-HostServiceAccountBL' 'msDS-AuthenticatedAtDC' = $Account.'msDS-AuthenticatedAtDC' 'msDS-AllowedToActOnBehalfOfOtherIdentity' = $Account.'msDS-AllowedToActOnBehalfOfOtherIdentity' 'PrincipalsAllowedToRetrieveManagedPassword' = $Account.'PrincipalsAllowedToRetrieveManagedPassword' 'PrincipalsAllowedToDelegateToAccount' = $Account.'PrincipalsAllowedToDelegateToAccount' 'msDS-GroupMSAMembershipAccess' = $Account.'msDS-GroupMSAMembership'.Access.IdentityReference.Value 'msDS-GroupMSAMembershipOwner' = $Account.'msDS-GroupMSAMembership'.Owner 'msDS-RevealedDSAs' = $Account.'msDS-RevealedDSAs' 'servicePrincipalName' = $Account.servicePrincipalName AccountNotDelegated = $Account.AccountNotDelegated TrustedForDelegation = $Account.TrustedForDelegation TrustedToAuthForDelegation = $Account.TrustedToAuthForDelegation AccountExpirationDate = $Account.AccountExpirationDate msDSSupportedEncryptionTypes = Get-ADEncryptionTypes -Value $Account.'msds-supportedencryptiontypes' PasswordNeverExpires = $Account.PasswordNeverExpires PasswordNotRequired = $Account.PasswordNotRequired LastLogonDate = $Account.LastLogonDate PasswordLastSet = $Account.PasswordLastSet WhenChanged = $Account.WhenChanged WhenCreated = $Account.WhenCreated SamAccountName = $Account.SamAccountName DistinguishedName = $Account.DistinguishedName 'msDS-GroupMSAMembership' = $Account.'msDS-GroupMSAMembership' } } } if ($PerDomain) { $Output } else { $Output.Values } } 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 -Verbose $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-WinADTrust { [alias('Get-WinADTrusts')] [cmdletBinding()] param([string] $Forest, [switch] $Recursive, [Parameter(DontShow)][int] $Nesting = -1, [Parameter(DontShow)][System.Collections.IDictionary] $UniqueTrusts) Begin { if ($Nesting -eq -1) { $UniqueTrusts = [ordered]@{} } } Process { $Nesting++ $ForestInformation = Get-WinADForest -Forest $Forest [Array] $Trusts = @(try { $TrustRelationship = $ForestInformation.GetAllTrustRelationships() foreach ($Trust in $TrustRelationship) { [ordered] @{Type = 'Forest' Details = $Trust ExecuteObject = $ForestInformation } } } catch { Write-Warning "Get-WinADForest - Can't process trusts for $Forest, error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" } foreach ($Domain in $ForestInformation.Domains) { $DomainInformation = Get-WinADDomain -Domain $Domain.Name try { $TrustRelationship = $DomainInformation.GetAllTrustRelationships() foreach ($Trust in $TrustRelationship) { [ordered] @{Type = 'Domain' Details = $Trust ExecuteObject = $DomainInformation } } } catch { Write-Warning "Get-WinADForest - Can't process trusts for $Domain, error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" } }) [Array] $Output = foreach ($Trust in $Trusts) { Write-Verbose "Get-WinADTrust - From: $($Trust.Details.SourceName) To: $($Trust.Details.TargetName) Nesting: $Nesting" $UniqueID1 = -join ($Trust.Details.SourceName, $Trust.Details.TargetName) $UniqueID2 = -join ($Trust.Details.TargetName, $Trust.Details.SourceName) if (-not $UniqueTrusts[$UniqueID1]) { $UniqueTrusts[$UniqueID1] = $true } else { Write-Verbose "Get-WinADTrust - Trust already on the list (From: $($Trust.Details.SourceName) To: $($Trust.Details.TargetName) Nesting: $Nesting)" continue } if (-not $UniqueTrusts[$UniqueID2]) { $UniqueTrusts[$UniqueID2] = $true } else { Write-Verbose "Get-WinADTrust - Trust already on the list (Reverse) (From: $($Trust.Details.TargetName) To: $($Trust.Details.SourceName) Nesting: $Nesting" continue } $TrustObject = Get-WinADTrustObject -Domain $Trust.ExecuteObject.Name -AsHashTable if ($TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Enable TGT DELEGATION") { $TGTDelegation = $true } elseif ($TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "No TGT DELEGATION") { $TGTDelegation = $false } else { $TGTDelegation = $false } $TrustStatus = Test-DomainTrust -Domain $Trust.Details.SourceName -TrustedDomain $Trust.Details.TargetName $GroupExists = Get-WinADObject -Identity 'S-1-5-32-544' -DomainName $Trust.Details.TargetName [PsCustomObject] @{'TrustSource' = $Trust.Details.SourceName 'TrustTarget' = $Trust.Details.TargetName 'TrustDirection' = $Trust.Details.TrustDirection.ToString() 'TrustBase' = $Trust.Type 'TrustType' = $Trust.Details.TrustType.ToString() 'TrustTypeAD' = $TrustObject[$Trust.Details.TargetName].TrustType 'Level' = $Nesting 'SuffixesIncluded' = (($Trust.Details.TopLevelNames | Where-Object { $_.Status -eq 'Enabled' }).Name) -join ', ' 'SuffixesExcluded' = $Trust.Details.ExcludedTopLevelNames.Name 'TrustAttributes' = $TrustObject[$Trust.Details.TargetName].TrustAttributes -join ', ' 'TrustStatus' = $TrustStatus.TrustStatus 'QueryStatus' = if ($GroupExists) { 'OK' } else { 'NOT OK' } 'ForestTransitive' = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Forest Transitive" 'SelectiveAuthentication' = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Cross Organization" 'SIDFilteringQuarantined' = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Quarantined Domain" 'DisallowTransitivity' = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Non Transitive" 'IntraForest' = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Within Forest" 'IsTGTDelegationEnabled' = $TGTDelegation 'UplevelOnly' = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "UpLevel Only" 'UsesAESKeys' = $TrustObject[$Trust.Details.TargetName].msDSSupportedEncryptionTypes -contains "AES128-CTS-HMAC-SHA1-96" -or $TrustObject[$Trust.Details.TargetName].msDSSupportedEncryptionTypes -contains 'AES256-CTS-HMAC-SHA1-96' 'UsesRC4Encryption' = $TrustObject[$Trust.Details.TargetName].TrustAttributes -contains "Uses RC4 Encryption" 'EncryptionTypes' = $TrustObject[$Trust.Details.TargetName].msDSSupportedEncryptionTypes -join ', ' 'TrustSourceDC' = $TrustStatus.TrustSourceDC 'TrustTargetDC' = $TrustStatus.TrustTargetDC 'ObjectGUID' = $TrustObject[$Trust.Details.TargetName].ObjectGuid 'ObjectSID' = $TrustObject[$Trust.Details.TargetName].ObjectSID 'Created' = $TrustObject[$Trust.Details.TargetName].WhenCreated 'Modified' = $TrustObject[$Trust.Details.TargetName].WhenChanged 'TrustDirectionText' = $TrustObject[$Trust.Details.TargetName].TrustDirectionText 'TrustTypeText' = $TrustObject[$Trust.Details.TargetName].TrustTypeText 'AdditionalInformation' = [ordered] @{'msDSSupportedEncryptionTypes' = $TrustObject[$Trust.Details.TargetName].msDSSupportedEncryptionTypes 'msDSTrustForestTrustInfo' = $TrustObject[$Trust.Details.TargetName].msDSTrustForestTrustInfo 'SuffixesInclude' = $Trust.Details.TopLevelNames 'SuffixesExclude' = $Trust.Details.ExcludedTopLevelNames 'TrustObject' = $TrustObject 'GroupExists' = $GroupExists } } } if ($Output -and $Output.Count -gt 0) { $Output } if ($Recursive) { foreach ($Trust in $Output) { if ($Trust.TrustType -notin 'TreeRoot', 'ParentChild') { Get-WinADTrust -Forest $Trust.TrustTarget -Recursive -Nesting $Nesting -UniqueTrusts $UniqueTrusts } } } } } function Get-WinADTrustLegacy { [CmdletBinding()] param([string] $Forest, [alias('Domain')][string[]] $IncludeDomains, [string[]] $ExcludeDomains, [switch] $Display, [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $Unique) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $UniqueTrusts = [ordered]@{} 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) { if ($Unique) { $UniqueID1 = -join ($Domain, $Trust.trustPartner) $UniqueID2 = -join ($Trust.trustPartner, $Domain) if (-not $UniqueTrusts[$UniqueID1]) { $UniqueTrusts[$UniqueID1] = $true } else { continue } if (-not $UniqueTrusts[$UniqueID2]) { $UniqueTrusts[$UniqueID2] = $true } else { continue } } $TrustWMI = $TrustStatatuses | & { process { if ($_.TrustedDomain -eq $Trust.Target) { $_ } } } if ($Display) { [PsCustomObject] @{'Trust Source' = $Domain 'Trust Target' = $Trust.Target 'Trust Direction' = $Trust.Direction.ToString() 'Trust Attributes' = if ($Trust.TrustAttributes -is [int]) { (Get-ADTrustAttributes -Value $Trust.TrustAttributes) -join '; ' } 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.ToString() '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.ToString() '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.ToString() '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-WinADUsers { [cmdletBinding()] param([alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $PerDomain) if (-not $Script:Cache) { $Script:Cache = [ordered] @{} $Script:AllUsers = [ordered] @{} } $Script:AllContacts = [ordered] @{} $Today = Get-Date $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $Properties = @('DistinguishedName', 'mail', 'LastLogonDate', 'PasswordLastSet', 'DisplayName', 'Manager', 'Description', 'PasswordNeverExpires', 'PasswordNotRequired', 'PasswordExpired', 'UserPrincipalName', 'SamAccountName', 'CannotChangePassword', 'TrustedForDelegation', 'TrustedToAuthForDelegation', 'msExchMailboxGuid', 'msExchRemoteRecipientType', 'msExchRecipientTypeDetails', 'msExchRecipientDisplayType', 'pwdLastSet', "msDS-UserPasswordExpiryTimeComputed", 'WhenCreated', 'WhenChanged') $AllUsers[$Domain] = Get-ADUser -Filter * -Server $QueryServer -Properties $Properties foreach ($Domain In $ForestInformation.Domains) { $AllUsers[$Domain] = Get-ADUser -Filter * -Properties $Properties -Server $ForestInformation['QueryServers'][$Domain].HostName[0] } foreach ($Domain In $ForestInformation.Domains) { $AllContacts[$Domain] = Get-ADObject -Filter 'objectClass -eq "contact"' -Properties SamAccountName, Mail, Name, DistinguishedName, WhenChanged, Whencreated, DisplayName } } if (-not $Script:Cache -or $Script:Cache.Count -eq 0) { $Script:Cache = @{} foreach ($Domain in $AllUsers.Keys) { foreach ($U in $AllUsers[$Domain]) { $Script:Cache[$U.DistinguishedName] = $U } } foreach ($Domain in $AllContacts.Keys) { foreach ($C in $AllContacts[$Domain]) { $Script:Cache[$C.DistinguishedName] = $C } } } $Output = [ordered] @{} foreach ($Domain in $ForestInformation.Domains) { if (-not $Script:Cache) { $Script:Cache = @{} foreach ($Domain in $AllUsers.Keys) { foreach ($U in $AllUsers[$Domain]) { $Script:Cache[$U.DistinguishedName] = $U } } foreach ($Domain in $AllComputers.Keys) { foreach ($C in $AllComputers[$Domain]) { $Script:Cache[$C.DistinguishedName] = $C } } } $Output[$Domain] = foreach ($User in $AllUsers[$Domain]) { $UserLocation = ($User.DistinguishedName -split ',').Replace('OU=', '').Replace('CN=', '').Replace('DC=', '') $Region = $UserLocation[-4] $Country = $UserLocation[-5] if ($User.LastLogonDate) { $LastLogonDays = $( - $($User.LastLogonDate - $Today).Days) } else { $LastLogonDays = $null } if ($User.PasswordLastSet) { $PasswordLastDays = $( - $($User.PasswordLastSet - $Today).Days) } else { $PasswordLastDays = $null } if ($User.Manager) { $Manager = $Cache[$User.Manager].DisplayName $ManagerSamAccountName = $Cache[$User.Manager].SamAccountName $ManagerEmail = $Cache[$User.Manager].Mail $ManagerEnabled = $Cache[$User.Manager].Enabled $ManagerLastLogon = $Cache[$User.Manager].LastLogonDate if ($ManagerLastLogon) { $ManagerLastLogonDays = $( - $($ManagerLastLogon - $Today).Days) } else { $ManagerLastLogonDays = $null } $ManagerStatus = if ($ManagerEnabled -eq $true) { 'Enabled' } elseif ($ManagerEnabled -eq $false) { 'Disabled' } else { 'Not available' } } else { if ($User.ObjectClass -eq 'user') { $ManagerStatus = 'Missing' } else { $ManagerStatus = 'Not available' } $Manager = $null $ManagerSamAccountName = $null $ManagerEmail = $null $ManagerEnabled = $null $ManagerLastLogon = $null $ManagerLastLogonDays = $null } if ($User."msDS-UserPasswordExpiryTimeComputed" -ne 9223372036854775807) { try { $DateExpiry = ([datetime]::FromFileTime($User."msDS-UserPasswordExpiryTimeComputed")) } catch { $DateExpiry = $User."msDS-UserPasswordExpiryTimeComputed" } try { $DaysToExpire = (New-TimeSpan -Start (Get-Date) -End ([datetime]::FromFileTime($User."msDS-UserPasswordExpiryTimeComputed"))).Days } catch { $DaysToExpire = $null } $PasswordNeverExpires = $User.PasswordNeverExpires } else { $PasswordNeverExpires = $true } if ($PasswordNeverExpires -or $null -eq $User.PasswordLastSet) { $DateExpiry = $null $DaysToExpire = $null } if ($User.'msExchMailboxGuid') { $HasMailbox = $true } else { $HasMailbox = $false } $msExchRecipientTypeDetails = Convert-ExchangeRecipient -msExchRecipientTypeDetails $User.msExchRecipientTypeDetails $msExchRecipientDisplayType = Convert-ExchangeRecipient -msExchRecipientDisplayType $User.msExchRecipientDisplayType $msExchRemoteRecipientType = Convert-ExchangeRecipient -msExchRemoteRecipientType $User.msExchRemoteRecipientType [PSCustomObject] @{Name = $User.Name SamAccountName = $User.SamAccountName Domain = $Domain WhenChanged = $User.WhenChanged Enabled = $User.Enabled ObjectClass = $User.ObjectClass HasMailbox = $HasMailbox MustChangePasswordAtLogon = if ($User.pwdLastSet -eq 0 -and $User.PasswordExpired -eq $true) { $true } else { $false } PasswordNeverExpires = $PasswordNeverExpires PasswordNotRequired = $User.PasswordNotRequired LastLogonDays = $LastLogonDays PasswordLastDays = $PasswordLastDays DaysToExpire = $DaysToExpire ManagerStatus = $ManagerStatus Manager = $Manager ManagerSamAccountName = $ManagerSamAccountName ManagerEmail = $ManagerEmail ManagerLastLogonDays = $ManagerLastLogonDays Level0 = $Region Level1 = $Country DistinguishedName = $User.DistinguishedName LastLogonDate = $User.LastLogonDate PasswordLastSet = $User.PasswordLastSet PasswordExpiresOn = $DateExpiry PasswordExpired = $User.PasswordExpired CannotChangePassword = $User.CannotChangePassword AccountTrustedForDelegation = $User.AccountTrustedForDelegation ManagerDN = $User.Manager ManagerLastLogon = $ManagerLastLogon Group = $Group Description = $User.Description UserPrincipalName = $User.UserPrincipalName RecipientTypeDetails = $msExchRecipientTypeDetails RecipientDisplayType = $msExchRecipientDisplayType RemoteRecipientType = $msExchRemoteRecipientType WhenCreated = $User.WhenCreated } } } if ($PerDomain) { $Output } else { $Output.Values } } 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 Invoke-ADEssentials { [cmdletBinding()] param([string] $FilePath, [Parameter(Position = 0)][string[]] $Type, [switch] $PassThru, [switch] $HideHTML, [switch] $HideSteps, [switch] $ShowError, [switch] $ShowWarning, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $Online) Reset-ADEssentialsStatus $Script:AllUsers = [ordered] @{} $Script:Cache = [ordered] @{} $Script:Reporting = [ordered] @{} $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Invoke-ADEssentials' -RepositoryOwner 'evotecit' -RepositoryName 'ADEssentials' $Script:Reporting['Settings'] = @{ShowError = $ShowError.IsPresent ShowWarning = $ShowWarning.IsPresent HideSteps = $HideSteps.IsPresent } Write-Color '[i]', "[ADEssentials] ", 'Version', ' [Informative] ', $Script:Reporting['Version'] -Color Yellow, DarkGray, Yellow, DarkGray, Magenta $Supported = [System.Collections.Generic.List[string]]::new() [Array] $NotSupported = foreach ($T in $Type) { if ($T -notin $Script:ADEssentialsConfiguration.Keys) { $T } else { $Supported.Add($T) } } if ($Supported) { Write-Color '[i]', "[ADEssentials] ", 'Supported types', ' [Informative] ', "Chosen by user: ", ($Supported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta } if ($NotSupported) { Write-Color '[i]', "[ADEssentials] ", 'Not supported types', ' [Informative] ', "Following types are not supported: ", ($NotSupported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta Write-Color '[i]', "[ADEssentials] ", 'Not supported types', ' [Informative] ', "Please use one/multiple from the list: ", ($Script:ADEssentialsConfiguration.Keys -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta return } $DisplayForest = if ($Forest) { $Forest } else { 'Not defined. Using current one' } $DisplayIncludedDomains = if ($IncludeDomains) { $IncludeDomains -join "," } else { 'Not defined. Using all domains of forest' } $DisplayExcludedDomains = if ($ExcludeDomains) { $ExcludeDomains -join ',' } else { 'No exclusions provided' } Write-Color '[i]', "[ADEssentials] ", 'Domain Information', ' [Informative] ', "Forest: ", $DisplayForest -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta Write-Color '[i]', "[ADEssentials] ", 'Domain Information', ' [Informative] ', "Included Domains: ", $DisplayIncludedDomains -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta Write-Color '[i]', "[ADEssentials] ", 'Domain Information', ' [Informative] ', "Excluded Domains: ", $DisplayExcludedDomains -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta if ($Type) { foreach ($T in $Script:ADEssentialsConfiguration.Keys) { $Script:ADEssentialsConfiguration[$T].Enabled = $false } foreach ($T in $Type) { $Script:ADEssentialsConfiguration[$T].Enabled = $true } } $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $Properties = @('DistinguishedName', 'mail', 'LastLogonDate', 'PasswordLastSet', 'DisplayName', 'Manager', 'Description', 'PasswordNeverExpires', 'PasswordNotRequired', 'PasswordExpired', 'UserPrincipalName', 'SamAccountName', 'CannotChangePassword', 'TrustedForDelegation', 'TrustedToAuthForDelegation', 'msExchMailboxGuid', 'msExchRemoteRecipientType', 'msExchRecipientTypeDetails', 'msExchRecipientDisplayType', 'pwdLastSet', "msDS-UserPasswordExpiryTimeComputed", 'WhenCreated', 'WhenChanged') $AllUsers[$Domain] = Get-ADUser -Filter * -Server $QueryServer -Properties $Properties } if (-not $Script:Cache) { $Script:Cache = @{} foreach ($Domain in $AllUsers.Keys) { foreach ($U in $AllUsers[$Domain]) { $Script:Cache[$U.DistinguishedName] = $U } } } foreach ($T in $Script:ADEssentialsConfiguration.Keys) { if ($Script:ADEssentialsConfiguration[$T].Enabled -eq $true) { $Script:Reporting[$T] = [ordered] @{Name = $Script:ADEssentialsConfiguration[$T].Name ActionRequired = $null Data = $null Exclusions = $null WarningsAndErrors = $null Time = $null Summary = $null Variables = Copy-Dictionary -Dictionary $Script:ADEssentialsConfiguration[$T]['Variables'] } if ($Exclusions) { if ($Exclusions -is [scriptblock]) { $Script:Reporting[$T]['ExclusionsCode'] = $Exclusions } if ($Exclusions -is [Array]) { $Script:Reporting[$T]['Exclusions'] = $Exclusions } } $TimeLogADEssentials = Start-TimeLog Write-Color -Text '[i]', '[Start] ', $($Script:ADEssentialsConfiguration[$T]['Name']) -Color Yellow, DarkGray, Yellow $OutputCommand = Invoke-Command -ScriptBlock $Script:ADEssentialsConfiguration[$T]['Execute'] -WarningVariable CommandWarnings -ErrorVariable CommandErrors -ArgumentList $Forest, $ExcludeDomains, $IncludeDomains if ($OutputCommand -is [System.Collections.IDictionary]) { $Script:Reporting[$T]['Data'] = $OutputCommand } else { $Script:Reporting[$T]['Data'] = [Array] $OutputCommand } Invoke-Command -ScriptBlock $Script:ADEssentialsConfiguration[$T]['Processing'] $Script:Reporting[$T]['WarningsAndErrors'] = @(if ($ShowWarning) { foreach ($War in $CommandWarnings) { [PSCustomObject] @{Type = 'Warning' Comment = $War Reason = '' TargetName = '' } } } if ($ShowError) { foreach ($Err in $CommandErrors) { [PSCustomObject] @{Type = 'Error' Comment = $Err Reason = $Err.CategoryInfo.Reason TargetName = $Err.CategoryInfo.TargetName } } }) $TimeEndADEssentials = Stop-TimeLog -Time $TimeLogADEssentials -Option OneLiner $Script:Reporting[$T]['Time'] = $TimeEndADEssentials Write-Color -Text '[i]', '[End ] ', $($Script:ADEssentialsConfiguration[$T]['Name']), " [Time to execute: $TimeEndADEssentials]" -Color Yellow, DarkGray, Yellow, DarkGray } } New-HTML -Author 'Przemysław Kłys' -TitleText 'ADEssentials Report' { New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLPanelStyle -BorderRadius 0px New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin New-HTMLHeader { New-HTMLSection -Invisible { New-HTMLSection { New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue } -JustifyContent flex-start -Invisible New-HTMLSection { New-HTMLText -Text "ADEssentials - $($Script:Reporting['Version'])" -Color Blue } -JustifyContent flex-end -Invisible } } if ($Type.Count -eq 1) { foreach ($T in $Script:ADEssentialsConfiguration.Keys) { if ($Script:ADEssentialsConfiguration[$T].Enabled -eq $true) { if ($Script:ADEssentialsConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ADEssentialsConfiguration[$T]['Summary'] } & $Script:ADEssentialsConfiguration[$T]['Solution'] } } } else { foreach ($T in $Script:ADEssentialsConfiguration.Keys) { if ($Script:ADEssentialsConfiguration[$T].Enabled -eq $true) { if ($Script:ADEssentialsConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ADEssentialsConfiguration[$T]['Summary'] } New-HTMLTab -Name $Script:ADEssentialsConfiguration[$T]['Name'] { & $Script:ADEssentialsConfiguration[$T]['Solution'] } } } } } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath Reset-ADEssentialsStatus } [scriptblock] $SourcesAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $Script:ADEssentialsConfiguration.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" } } Register-ArgumentCompleter -CommandName Invoke-ADEssentials -ParameterName Type -ScriptBlock $SourcesAutoCompleter 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)" try { $SubACL.ACL.RemoveAccessRule($AccessRuleToRemove) $true } catch { Write-Warning "Remove-ADACL - Removing permissions for $($SubACL.DistinguishedName) failed: $($_.Exception.Message)" $false } } }) if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) { Write-Verbose "Remove-ADACL - Saving permissions for $($SubACL.DistinguishedName)" try { Set-Acl -Path $SubACL.Path -AclObject $SubACL.ACL -ErrorAction Stop } catch { Write-Warning "Remove-ADACL - Saving permissions for $($SubACL.DistinguishedName) failed: $($_.Exception.Message)" } } 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, [string] $PartialMatchDistinguishedName, [string[]] $IncludeObjectClass, [string[]] $ExcludeObjectClass, [int] $LimitProcessing = [int32]::MaxValue) $getWinADDuplicateObjectSplat = @{Forest = $Forest ExcludeDomains = $ExcludeDomains IncludeDomains = $IncludeDomains IncludeObjectClass = $IncludeObjectClass ExcludeObjectClass = $ExcludeObjectClass PartialMatchDistinguishedName = $PartialMatchDistinguishedName } $Count = 0 $DuplicateObjects = Get-WinADDuplicateObject @getWinADDuplicateObjectSplat foreach ($Duplicate in $DuplicateObjects | Select-Object -First $LimitProcessing) { $Count++ try { Write-Verbose "Remove-WinADDuplicateObject - [$Count/$($DuplicateObjects.Count)] Deleting $($Duplicate.ConflictDN) / $($Duplicate.DomainName) via GUID: $($Duplicate.ObjectGUID)" Remove-ADObject -Identity $Duplicate.ObjectGUID -Recursive -ErrorAction Stop -Confirm:$false -Server $Duplicate.DomainName } catch { Write-Warning "Remove-WinADDuplicateObject - [$Count/$($DuplicateObjects.Count)] Deleting $($Duplicate.ConflictDN) / $($Duplicate.DomainName) via GUID: $($Duplicate.ObjectGUID) failed 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-WinADACLConfigurationOwner { <# .SYNOPSIS Fixes all owners of certain object type (site,subnet,sitelink,interSiteTransport,wellKnownSecurityPrincipal) to be Enterprise Admins .DESCRIPTION Fixes all owners of certain object type (site,subnet,sitelink,interSiteTransport,wellKnownSecurityPrincipal) to be Enterprise Admins .PARAMETER ObjectType Gets owners from one or multiple types (and only that type). Possible choices are sites, subnets, interSiteTransport, siteLink, wellKnownSecurityPrincipals .PARAMETER ContainerType Gets owners from one or multiple types (including containers and anything below it). Possible choices are sites, subnets, interSiteTransport, siteLink, wellKnownSecurityPrincipals, services .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER LimitProcessing Provide limit of objects that will be fixed in a single run .EXAMPLE An example .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'ObjectType', SupportsShouldProcess)] param([parameter(ParameterSetName = 'ObjectType', Mandatory)][ValidateSet('site', 'subnet', 'interSiteTransport', 'siteLink', 'wellKnownSecurityPrincipal')][string[]] $ObjectType, [parameter(ParameterSetName = 'FolderType', Mandatory)][ValidateSet('site', 'subnet', 'interSiteTransport', 'siteLink', 'wellKnownSecurityPrincipal', 'service')][string[]] $ContainerType, [string] $Forest, [System.Collections.IDictionary] $ExtendedForestInformation, [int] $LimitProcessing = [int32]::MaxValue) $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -ExtendedForestInformation $ForestInformation $getWinADACLConfigurationSplat = @{ContainerType = $ContainerType ObjectType = $ObjectType Owner = $true Forest = $Forest ExtendedForestInformation = $ExtendedForestInformation } Remove-EmptyValue -Hashtable $getWinADACLConfigurationSplat Get-WinADACLConfiguration @getWinADACLConfigurationSplat | Where-Object { if ($_.OwnerType -ne 'Administrative' -and $_.OwnerType -ne 'WellKnownAdministrative') { $_ } } | Select-Object -First $LimitProcessing | ForEach-Object { $ADObject = $_ $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $_.DistinguishedName $EnterpriseAdmin = $ADAdministrativeGroups[$DomainName]['EnterpriseAdmins'] Set-ADACLOwner -ADObject $ADObject.DistinguishedName -Principal $EnterpriseAdmin } } function Repair-WinADEmailAddress { [CmdletBinding(SupportsShouldProcess)] param([Microsoft.ActiveDirectory.Management.ADAccount] $ADUser, [string] $ToEmail, [switch] $Display, [Array] $AddSecondary) $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 - User $($ADUser.SamAccountName) is missing properties ($($RequiredProperties -join ',')) which 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 } if (-not $ToEmail) { $ExpectedUser.EmailAddress = $ProcessUser.EmailAddress $ExpectedUser.Primary = $ProcessUser.Primary if (-not $ExpectedUser.Primary -and $ExpectedUser.EmailAddress) { $ExpectedUser.Primary = $ExpectedUser.EmailAddress } } $MakePrimary = "SMTP:$($ExpectedUser.EmailAddress)" $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".ToLower()) } foreach ($Email in $AddSecondary) { if ($Email -like 'smtp:*') { $ProxyAddresses.Add($Email.ToLower()) } else { $ProxyAddresses.Add("smtp:$Email".ToLower()) } } $Summary['EmailAddress'] = $ExpectedUser.EmailAddress 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 $ExpectedUser.EmailAddress -ErrorAction Stop $Summary['EmailAddressStatus'] = 'Success' $Summary['EmailAddressError'] = '' } catch { $Summary['EmailAddressStatus'] = 'Failed' $Summary['EmailAddressError'] = $_.Exception.Message } } else { $Summary['EmailAddressStatus'] = 'Whatif' $Summary['EmailAddressError'] = '' } } $UniqueProxyList = [System.Collections.Generic.List[string]]::new() foreach ($Proxy in $ProxyAddresses) { if ($UniqueProxyList -notcontains $Proxy) { $UniqueProxyList.Add($Proxy) } } [string[]] $ExpectedProxyAddresses = ($UniqueProxyList | Sort-Object | ForEach-Object { $_ }) [string[]] $CurrentProxyAddresses = ($ADUser.ProxyAddresses | Sort-Object | ForEach-Object { $_ }) $Summary['ProxyAddresses'] = $ExpectedProxyAddresses -join ';' if (Compare-Object -ReferenceObject $ExpectedProxyAddresses -DifferenceObject $CurrentProxyAddresses -CaseSensitive) { 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 Repair-WinADForestControllerInformation { [cmdletBinding(SupportsShouldProcess)] param([parameter(Mandatory)][validateSet('Owner', 'Manager')][string[]] $Type, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [int] $LimitProcessing) $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation if (-not $ADAdministrativeGroups) { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation } $Fixed = 0 $DCs = Get-WinADForestControllerInformation -Forest $Forest -ExtendedForestInformation $ForestInformation -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains | ForEach-Object { $DC = $_ $Done = $false if ($Type -contains 'Owner') { if ($DC.OwnerType -ne 'Administrative') { Write-Verbose -Message "Repair-WinADForestControllerInformation - Fixing (Owner) [$($DC.DomainName)]($Count/$($DCs.Count)) $($DC.DNSHostName)" $Principal = $ADAdministrativeGroups[$DC.DomainName]['DomainAdmins'] Set-ADACLOwner -ADObject $DC.DistinguishedName -Principal $Principal $Done = $true } } if ($Type -contains 'Manager') { if ($null -ne $DC.ManagedBy) { Write-Verbose -Message "Repair-WinADForestControllerInformation - Fixing (Manager) [$($DC.DomainName)]($Count/$($DCs.Count)) $($DC.DNSHostName)" Set-ADComputer -Identity $DC.DistinguishedName -Clear ManagedBy -Server $ForestInformation['QueryServers'][$DC.DomainName]['HostName'][0] $Done = $true } } if ($Done -eq $true) { $Fixed++ } if ($LimitProcessing -ne 0 -and $Fixed -eq $LimitProcessing) { break } } } 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-DnsServerIP { [alias('Set-WinDNSServerIP')] [cmdletbInding(SupportsShouldProcess)] param([string[]] $ComputerName, [string[]] $DnsIpAddress, [pscredential] $Credential) foreach ($Computer in $Computers) { try { if ($Credential) { $CimSession = New-CimSession -ComputerName $Computer -Credential $Credential -Authentication Negotiate -ErrorAction Stop } else { $CimSession = New-CimSession -ComputerName $Computer -ErrorAction Stop -Authentication Negotiate } } catch { Write-Warning "Couldn't authorize session to $Computer. Error $($_.Exception.Message). Skipping." continue } $Adapters = Get-CimData -Class Win32_NetworkAdapterConfiguration -ComputerName $Computer | Where-Object { $_.DHCPEnabled -ne 'True' -and $null -ne $_.DNSServerSearchOrder } if ($Adapters) { $Text = "Setting DNS to $($DNSIPAddress -join ', ')" if ($PSCmdlet.ShouldProcess($Computer, $Text)) { if ($Adapters) { try { $Adapters | Set-DnsClientServerAddress -ServerAddresses $DnsIpAddress -CimSession $CimSession } catch { Write-Warning "Couldn't fix adapters with IP Address for $Computer. Error $($_.Exception.Message)" continue } } Get-DNSServerIP -ComputerName $Computer } } } } 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 Show-WinADGroupMember { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Identity Group Name to search for .PARAMETER Conditions Provides ability to control look and feel of tables across HTML .PARAMETER FilePath Path to HTML file where it's saved. If not given temporary path is used .PARAMETER HideAppliesTo Allows to define to which diagram HideComputers,HideUsers,HideOther applies to .PARAMETER HideComputers Hide computers from diagrams - useful for performance reasons .PARAMETER HideUsers Hide users from diagrams - useful for performance reasons .PARAMETER HideOther Hide other objects from diagrams - useful for performance reasons .PARAMETER Online Forces use of online CDN for JavaScript/CSS which makes the file smaller. Default - use offline. .PARAMETER HideHTML Prevents HTML from opening up after command is done. Useful for automation .PARAMETER DisableBuiltinConditions Disables table coloring allowing user to define it's own conditions .PARAMETER AdditionalStatistics Adds additional data to Self object. It includes count for NestingMax, NestingGroup, NestingGroupSecurity, NestingGroupDistribution. It allows for easy filtering where we expect security groups only when there are nested distribution groups. .PARAMETER Summary Adds additional tab with all groups together on two diagrams .PARAMETER SummaryOnly Adds one tab with all groups together on two diagrams .EXAMPLE Show-WinADGroupMember -GroupName 'Domain Admins' -FilePath $PSScriptRoot\Reports\GroupMembership1.html -Online -Verbose .EXAMPLE Show-WinADGroupMember -GroupName 'Test-Group', 'Domain Admins' -FilePath $PSScriptRoot\Reports\GroupMembership2.html -Online -Verbose .EXAMPLE Show-WinADGroupMember -GroupName 'GDS-TestGroup4' -FilePath $PSScriptRoot\Reports\GroupMembership3.html -Summary -Online -Verbose .EXAMPLE Show-WinADGroupMember -GroupName 'Group1' -Verbose -Online .NOTES General notes #> [alias('Show-ADGroupMember')] [cmdletBinding(DefaultParameterSetName = 'Default')] param([Parameter(Position = 0)][alias('GroupName', 'Group')][Array] $Identity, [Parameter(Position = 1)][scriptblock] $Conditions, [string] $FilePath, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [switch] $HideComputers, [switch] $HideUsers, [switch] $HideOther, [switch] $Online, [switch] $HideHTML, [switch] $DisableBuiltinConditions, [switch] $AdditionalStatistics, [Parameter(ParameterSetName = 'Default')][switch] $Summary, [Parameter(ParameterSetName = 'SummaryOnly')][switch] $SummaryOnly) $VisualizeOnly = $false if ($FilePath -eq '') { $FilePath = Get-FileName -Extension 'html' -Temporary } $GroupsList = [System.Collections.Generic.List[object]]::new() New-HTML -TitleText "Visual Group Membership" { New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLTableOption -DataStore JavaScript New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey if ($Identity[0].GroupName) { $GroupMembersCache = [ordered] @{} $VisualizeOnly = $true foreach ($Entry in $Identity) { $IdentityGroupName = "($($Entry.GroupName) / $($Entry.GroupDomainName))" if (-not $GroupMembersCache[$IdentityGroupName]) { $GroupMembersCache[$IdentityGroupName] = [System.Collections.Generic.List[PSCustomObject]]::new() } $GroupMembersCache[$IdentityGroupName].Add($Entry) } [Array] $IdentityList = $GroupMembersCache.Keys } else { [Array] $IdentityList = $Identity } foreach ($Group in $IdentityList) { try { Write-Verbose "Show-WinADGroupMember - requesting $Group group nested membership" if ($VisualizeOnly) { $ADGroup = $GroupMembersCache[$Group] } else { $ADGroup = Get-WinADGroupMember -Group $Group -All -AddSelf -AdditionalStatistics:$AdditionalStatistics } if ($Summary -or $SummaryOnly) { foreach ($Object in $ADGroup) { $GroupsList.Add($Object) } } } catch { Write-Warning "Show-WinADGroupMember - Error processing group $Group. Skipping. Needs investigation why it failed. Error: $($_.Exception.Message)" continue } if ($ADGroup -and -not $SummaryOnly) { $GroupName = $ADGroup[0].GroupName $NetBIOSName = Convert-DomainFqdnToNetBIOS -DomainName $ADGroup[0].DomainName $FullName = "$NetBIOSName\$GroupName" $DataStoreID = -join ('table', (Get-RandomStringName -Size 10 -ToLower)) $DataTableID = -join ('table', (Get-RandomStringName -Size 10 -ToLower)) New-HTMLTab -TabName $FullName { New-HTMLTab -TabName 'Information' { New-HTMLSection -Title "Information for $GroupName" { New-HTMLTable -DataTable $ADGroup -Filtering -DataStoreID $DataStoreID { if (-not $DisableBuiltinConditions) { New-TableHeader -Names Name, SamAccountName, DomainName, DisplayName -Title 'Member' New-TableHeader -Names DirectMembers, DirectGroups, IndirectMembers, TotalMembers -Title 'Statistics' New-TableHeader -Names GroupType, GroupScope -Title 'Group Details' New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $false -Name Enabled -Operator eq New-TableCondition -BackgroundColor LightBlue -ComparisonType string -Value '' -Name ParentGroup -Operator eq -Row New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name CrossForest -Operator eq New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name CircularIndirect -Operator eq -Row New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name CircularDirect -Operator eq -Row } if ($Conditions) { & $Conditions } } } } New-HTMLTab -TabName 'Diagram Basic' { New-HTMLSection -Title "Diagram for $GroupName" { New-HTMLGroupDiagramDefault -ADGroup $ADGroup -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -DataTableID $DataTableID -ColumnID 1 -Online:$Online } } New-HTMLTab -TabName 'Diagram Hierarchy' { New-HTMLSection -Title "Diagram for $GroupName" { New-HTMLGroupDiagramHierachical -ADGroup $ADGroup -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -Online:$Online } } } } } if ($Summary -or $SummaryOnly) { New-HTMLTab -Name 'Summary' { New-HTMLTab -TabName 'Diagram Basic' { New-HTMLSection -Title "Diagram for Summary" { New-HTMLGroupDiagramSummary -ADGroup $GroupsList -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -DataTableID $DataTableID -ColumnID 1 -Online:$Online } } New-HTMLTab -TabName 'Diagram Hierarchy' { New-HTMLSection -Title "Diagram for Summary" { New-HTMLGroupDiagramSummaryHierarchical -ADGroup $GroupsList -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -Online:$Online } } } } } -Online:$Online -FilePath $FilePath -ShowHTML:(-not $HideHTML) } function Show-WinADGroupMemberOf { [alias('Show-ADGroupMemberOf')] [cmdletBinding(DefaultParameterSetName = 'Default')] param([Parameter(Position = 1)][scriptblock] $Conditions, [parameter(Position = 0, Mandatory)][string[]] $Identity, [string] $FilePath, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [Parameter(ParameterSetName = 'Default')][switch] $Summary, [Parameter(ParameterSetName = 'SummaryOnly')][switch] $SummaryOnly, [switch] $Online, [switch] $HideHTML, [switch] $DisableBuiltinConditions) if ($FilePath -eq '') { $FilePath = Get-FileName -Extension 'html' -Temporary } $GroupsList = [System.Collections.Generic.List[object]]::new() New-HTML -TitleText "Visual Object MemberOf" { New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLTableOption -DataStore JavaScript New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey foreach ($ADObject in $Identity) { try { Write-Verbose "Show-WinADObjectMember - requesting $Identity member of property" $MyObject = Get-WinADGroupMemberOf -Identity $ADObject -AddSelf if ($Summary -or $SummaryOnly) { foreach ($Object in $MyObject) { $GroupsList.Add($Object) } } } catch { Write-Warning "Show-WinADGroupMemberOf - Error processing group $Group. Skipping. Needs investigation why it failed. Error: $($_.Exception.Message)" continue } if ($MyObject -and -not $SummaryOnly) { $ObjectName = $MyObject[0].ObjectName $DataStoreID = -join ('table', (Get-RandomStringName -Size 10 -ToLower)) $DataTableID = -join ('table', (Get-RandomStringName -Size 10 -ToLower)) New-HTMLTab -TabName $ObjectName { New-HTMLTab -TabName 'Information' { New-HTMLSection -Title "Information for $ObjectName" { New-HTMLTable -DataTable $MyObject -Filtering -DataStoreID $DataStoreID { if (-not $DisableBuiltinConditions) { New-TableHeader -Names Name, SamAccountName, DomainName, DisplayName -Title 'Member' New-TableHeader -Names GroupType, GroupScope -Title 'Group Details' New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $false -Name Enabled -Operator eq New-TableCondition -BackgroundColor LightBlue -ComparisonType string -Value '' -Name ParentGroup -Operator eq -Row New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name CrossForest -Operator eq New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name Circular -Operator eq -Row } if ($Conditions) { & $Conditions } } } } New-HTMLTab -TabName 'Diagram Basic' { New-HTMLSection -Title "Diagram for $ObjectName" { New-HTMLGroupOfDiagramDefault -Identity $MyObject -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -DataTableID $DataTableID -ColumnID 1 -Online:$Online } } New-HTMLTab -TabName 'Diagram Hierarchy' { New-HTMLSection -Title "Diagram for $ObjectName" { New-HTMLGroupOfDiagramHierarchical -Identity $MyObject -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -Online:$Online } } } } } if ($Summary -or $SummaryOnly) { New-HTMLTab -Name 'Summary' { New-HTMLTab -TabName 'Diagram Basic' { New-HTMLSection -Title "Diagram for Summary" { New-HTMLGroupOfDiagramSummary -ADGroup $GroupsList -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -DataTableID $DataTableID -ColumnID 1 -Online:$Online } } New-HTMLTab -TabName 'Diagram Hierarchy' { New-HTMLSection -Title "Diagram for Summary" { New-HTMLGroupOfDiagramSummaryHierarchical -ADGroup $GroupsList -HideAppliesTo $HideAppliesTo -HideUsers:$HideUsers -HideComputers:$HideComputers -HideOther:$HideOther -Online:$Online } } } } } -Online:$Online -FilePath $FilePath -ShowHTML:(-not $HideHTML) } function Show-WinADTrust { [alias('Show-ADTrust', 'Show-ADTrusts', 'Show-WinADTrusts')] [cmdletBinding()] param([Parameter(Position = 0)][scriptblock] $Conditions, [switch] $Recursive, [string] $FilePath, [switch] $Online, [switch] $HideHTML, [switch] $DisableBuiltinConditions, [switch] $PassThru) if ($FilePath -eq '') { $FilePath = Get-FileName -Extension 'html' -Temporary } $Script:ADTrusts = @() New-HTML -TitleText "Visual Trusts" { New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLTableOption -DataStore HTML New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey $Script:ADTrusts = Get-WinADTrust -Recursive:$Recursive Write-Verbose "Show-WinADTrust - Found $($ADTrusts.Count) trusts" New-HTMLTab -TabName 'Summary' { New-HTMLSection -HeaderText 'Trusts Diagram' { New-HTMLDiagram -Height 'calc(50vh)' { New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion foreach ($Node in $AllNodes) { New-DiagramNode -Label $Node.'Trust' } foreach ($Trust in $ADTrusts) { New-DiagramNode -Label $Trust.'TrustSource' -IconSolid audio-description New-DiagramNode -Label $Trust.'TrustTarget' -IconSolid audio-description $newDiagramLinkSplat = @{From = $Trust.'TrustSource' To = $Trust.'TrustTarget' ColorOpacity = 0.7 } if ($Trust.'TrustDirection' -eq 'Disabled') {} elseif ($Trust.'TrustDirection' -eq 'Inbound') { $newDiagramLinkSplat.ArrowsFromEnabled = $true } elseif ($Trust.'TrustDirection' -eq 'Outbount') { $newDiagramLinkSplat.ArrowsToEnabled = $true New-DiagramLink @newDiagramLinkSplat } elseif ($Trust.'TrustDirection' -eq 'Bidirectional') { $newDiagramLinkSplat.ArrowsToEnabled = $true $newDiagramLinkSplat.ArrowsFromEnabled = $true } if ($Trust.IntraForest) { $newDiagramLinkSplat.Color = 'DarkSpringGreen' } if ($Trust.QueryStatus -eq 'OK' -or $Trust.TrustStatus -eq 'OK') { $newDiagramLinkSplat.Dashes = $false $newDiagramLinkSplat.FontColor = 'Green' } else { $newDiagramLinkSplat.Dashes = $true $newDiagramLinkSplat.FontColor = 'Red' } if ($Trust.IsTGTDelegationEnabled) { $newDiagramLinkSplat.Color = 'Red' $newDiagramLinkSplat.Label = "Delegation Enabled" } else { $newDiagramLinkSplat.Label = $Trust.QueryStatus } New-DiagramLink @newDiagramLinkSplat } } } New-HTMLSection -Title "Information about Trusts" { New-HTMLTable -DataTable $ADTrusts -Filtering { if (-not $DisableBuiltinConditions) { New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType string -Value 'OK' -Name TrustStatus -Operator eq New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType string -Value 'OK' -Name QueryStatus -Operator eq New-TableCondition -BackgroundColor CoralRed -ComparisonType string -Value 'NOT OK' -Name QueryStatus -Operator eq New-TableCondition -BackgroundColor CoralRed -ComparisonType bool -Value $true -Name IsTGTDelegationEnabled -Operator eq } if ($Conditions) { & $Conditions } } -DataTableID 'DT-TrustsInformation' } } $TrustCache = [ordered]@{} foreach ($Trust in $ADTrusts) { Write-Verbose "Show-WinADTrust - Processing $($Trust.TrustSource) to $($Trust.TrustTarget)" if (-not $TrustCache[$Trust.TrustSource]) { Write-Verbose "Show-WinADTrust - Creating cache for $($Trust.TrustSource)" $TrustCache[$Trust.TrustSource] = [System.Collections.Generic.List[PSCustomObject]]::new() } $TrustCache[$Trust.TrustSource].Add($Trust) } foreach ($Source in $TrustCache.Keys) { New-HTMLTab -TabName "Source $($Source.ToUpper())" { foreach ($Trust in $TrustCache[$Source]) { if ($Trust.QueryStatus -eq 'OK' -or $Trust.TrustStatus -eq 'OK') { $IconColor = 'MediumSeaGreen' $IconSolid = 'smile' } else { $IconColor = 'CoralRed' $IconSolid = 'angry' } New-HTMLTab -TabName "Target $($Trust.TrustTarget.ToUpper())" -IconColor $IconColor -IconSolid $IconSolid -TextColor $IconColor { New-HTMLSection -Invisible { New-HTMLSection -Title "Trust Information" { New-HTMLTable -DataTable $Trust { New-TableHeader -Names Name, Value -Title 'Trust Information' } -Transpose -HideFooter -DisablePaging -Buttons copyHtml5, excelHtml5, pdfHtml5 } New-HTMLSection -Invisible -Wrap wrap { New-HTMLSection -Title "Name suffix status" { New-HTMLTable -DataTable $Trust.AdditionalInformation.msDSTrustForestTrustInfo -Filtering { if ($Trust.AdditionalInformation.msDSTrustForestTrustInfo.Count -gt 0) { New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType string -Value 'Enabled' -Name Status -Operator eq -Row New-TableCondition -BackgroundColor CoralRed -ComparisonType string -Value 'Enabled' -Name Status -Operator ne -Row } } } New-HTMLSection -Title "Name suffix routing (include)" { New-HTMLTable -DataTable $Trust.AdditionalInformation.SuffixesInclude -Filtering { if ($Trust.AdditionalInformation.SuffixesInclude.Count -gt 0) { New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType string -Value 'Enabled' -Name Status -Operator eq -Row New-TableCondition -BackgroundColor CoralRed -ComparisonType string -Value 'Enabled' -Name Status -Operator ne -Row } } } New-HTMLSection -Title "Name suffix routing (exclude)" { New-HTMLTable -DataTable $Trust.AdditionalInformation.SuffixesExclude -Filtering { if ($Trust.AdditionalInformation.SuffixesExclude.Count -gt 0) { New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType string -Value 'Enabled' -Name Status -Operator eq -Row New-TableCondition -BackgroundColor CoralRed -ComparisonType string -Value 'Enabled' -Name Status -Operator ne -Row } } } } } } } } } } -Online:$Online -FilePath $FilePath -ShowHTML:(-not $HideHTML) if ($PassThru) { $Script:ADTrusts } } 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' SiteLinksCrossSiteUseNotify = 'No sitelinks, single DC' SiteLinksCrossSiteNotUseNotify = 'No sitelinks, single DC' SiteLinksSameSiteUseNotify = 'No sitelinks, single DC' SiteLinksSameSiteNotUseNotify = 'No sitelinks, single DC' SiteLinksDisabled = 'No sitelinks, single DC' SiteLinksEnabled = 'No sitelinks, single DC' SiteLinksCrossSiteUseNotifyCount = 0 SiteLinksCrossSiteNotUseNotifyCount = 0 SiteLinksSameSiteUseNotifyCount = 0 SiteLinksSameSiteNotUseNotifyCount = 0 SiteLinksManualCount = 0 SiteLinksAutomaticCount = 0 SiteLinksDisabledCount = 0 SiteLinksEnabledCount = 0 SiteLinksTotalCount = 0 SiteLinksTotalActiveCount = 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') [Array] $LinksManual = foreach ($Link in $Collection[0]) { "$($Link.ServerFrom) to $($Link.ServerTo)" } [Array] $LinksAutomatic = foreach ($Link in $Collection[1]) { "$($Link.ServerFrom) to $($Link.ServerTo)" } $LinksUsingNotificationsUnnessecary = [System.Collections.Generic.List[string]]::new() $LinksUsingNotifications = [System.Collections.Generic.List[string]]::new() $LinksNotUsingNotifications = [System.Collections.Generic.List[string]]::new() $LinksUsingNotificationsWhichIsOk = [System.Collections.Generic.List[string]]::new() $DisabledLinks = [System.Collections.Generic.List[string]]::new() $EnabledLinks = [System.Collections.Generic.List[string]]::new() foreach ($Link in $SiteLinks) { if ($Link.EnabledConnection -eq $true) { $EnabledLinks.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } else { $DisabledLinks.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } if ($Link.SiteFrom -eq $Link.SiteTo) { if ($Link.Options -contains 'UseNotify') { $LinksUsingNotificationsUnnessecary.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } else { $LinksUsingNotificationsWhichIsOk.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } } else { if ($Link.Options -contains 'UseNotify') { $LinksUsingNotifications.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } else { $LinksNotUsingNotifications.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } } } [ordered] @{SiteLinksManual = if ($Splitter -eq '') { $LinksManual } else { $LinksManual -join $Splitter } SiteLinksAutomatic = if ($Splitter -eq '') { $LinksAutomatic } else { $LinksAutomatic -join $Splitter } SiteLinksCrossSiteUseNotify = if ($Splitter -eq '') { $LinksUsingNotifications } else { $LinksUsingNotifications -join $Splitter } SiteLinksCrossSiteNotUseNotify = if ($Splitter -eq '') { $LinksNotUsingNotifications } else { $LinksNotUsingNotifications -join $Splitter } SiteLinksSameSiteUseNotify = if ($Splitter -eq '') { $LinksUsingNotificationsUnnessecary } else { $LinksUsingNotificationsUnnessecary -join $Splitter } SiteLinksSameSiteNotUseNotify = if ($Splitter -eq '') { $LinksUsingNotificationsWhichIsOk } else { $LinksUsingNotificationsWhichIsOk -join $Splitter } SiteLinksDisabled = if ($Splitter -eq '') { $DisabledLinks } else { $DisabledLinks -join $Splitter } SiteLinksEnabled = if ($Splitter -eq '') { $EnabledLinks } else { $EnabledLinks -join $Splitter } SiteLinksCrossSiteUseNotifyCount = $LinksUsingNotifications.Count SiteLinksCrossSiteNotUseNotifyCount = $LinksNotUsingNotifications.Count SiteLinksSameSiteUseNotifyCount = $LinksUsingNotificationsUnnessecary.Count SiteLinksSameSiteNotUseNotifyCount = $LinksUsingNotificationsWhichIsOk.Count SiteLinksManualCount = $Collection[0].Count SiteLinksAutomaticCount = $Collection[1].Count SiteLinksDisabledCount = $DisabledLinks.Count SiteLinksEnabledCount = $EnabledLinks.Count SiteLinksTotalCount = $SiteLinks.Count SiteLinksTotalActiveCount = ($SiteLinks | Where-Object { $_.EnabledConnection -eq $true }).Count Comment = 'OK' } } else { [ordered] @{SiteLinksManual = 'No sitelinks' SiteLinksAutomatic = 'No sitelinks' SiteLinksCrossSiteUseNotify = 'No sitelinks' SiteLinksCrossSiteNotUseNotify = 'No sitelinks' SiteLinksSameSiteUseNotify = 'No sitelinks' SiteLinksSameSiteNotUseNotify = 'No sitelinks' SiteLinksDisabled = 'No sitelinks' SiteLinksEnabled = 'No sitelinks' SiteLinksCrossSiteUseNotifyCount = 0 SiteLinksCrossSiteNotUseNotifyCount = 0 SiteLinksSameSiteUseNotifyCount = 0 SiteLinksSameSiteNotUseNotifyCount = 0 SiteLinksManualCount = 0 SiteLinksAutomaticCount = 0 SiteLinksDisabledCount = 0 SiteLinksEnabledCount = 0 SiteLinksTotalCount = 0 SiteLinksTotalActiveCount = 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 { <# .SYNOPSIS Tests LDAP connectivity to one ore more servers. .DESCRIPTION Tests LDAP connectivity to one ore more servers. It's able to gather certificate information which provides useful information. .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExcludeDomainControllers Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER IncludeDomainControllers Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER SkipRODC Skip Read-Only Domain Controllers. By default all domain controllers are included. .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER ComputerName Provide FQDN, IpAddress or NetBIOS name to test LDAP connectivity. This can be used instead of targetting Forest/Domain specific LDAP Servers .PARAMETER GCPortLDAP Global Catalog Port for LDAP. If not defined uses default 3268 port. .PARAMETER GCPortLDAPSSL Global Catalog Port for LDAPs. If not defined uses default 3269 port. .PARAMETER PortLDAP LDAP port. If not defined uses default 389 .PARAMETER PortLDAPS LDAPs port. If not defined uses default 636 .PARAMETER VerifyCertificate Binds to LDAP and gathers information about certificate available .PARAMETER Credential Allows to define credentials. This switches authentication for LDAP Binding from Kerberos to Basic .EXAMPLE Test-LDAP -ComputerName 'AD1' -VerifyCertificate | Format-Table * .EXAMPLE Test-LDAP -VerifyCertificate -SkipRODC | Format-Table * .NOTES General notes #> [CmdletBinding(DefaultParameterSetName = 'Forest')] param ([Parameter(ParameterSetName = 'Forest')][alias('ForestName')][string] $Forest, [Parameter(ParameterSetName = 'Forest')][string[]] $ExcludeDomains, [Parameter(ParameterSetName = 'Forest')][string[]] $ExcludeDomainControllers, [Parameter(ParameterSetName = 'Forest')][alias('Domain', 'Domains')][string[]] $IncludeDomains, [Parameter(ParameterSetName = 'Forest')][alias('DomainControllers')][string[]] $IncludeDomainControllers, [Parameter(ParameterSetName = 'Forest')][switch] $SkipRODC, [Parameter(ParameterSetName = 'Forest')][System.Collections.IDictionary] $ExtendedForestInformation, [alias('Server', 'IpAddress')][Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline, Mandatory, ParameterSetName = 'Computer')][string[]]$ComputerName, [Parameter(ParameterSetName = 'Forest')] [Parameter(ParameterSetName = 'Computer')] [int] $GCPortLDAP = 3268, [Parameter(ParameterSetName = 'Forest')] [Parameter(ParameterSetName = 'Computer')] [int] $GCPortLDAPSSL = 3269, [Parameter(ParameterSetName = 'Forest')] [Parameter(ParameterSetName = 'Computer')] [int] $PortLDAP = 389, [Parameter(ParameterSetName = 'Forest')] [Parameter(ParameterSetName = 'Computer')] [int] $PortLDAPS = 636, [Parameter(ParameterSetName = 'Forest')] [Parameter(ParameterSetName = 'Computer')] [switch] $VerifyCertificate, [Parameter(ParameterSetName = 'Forest')] [Parameter(ParameterSetName = 'Computer')] [PSCredential] $Credential) begin { Add-Type -Assembly System.DirectoryServices.Protocols if (-not $ComputerName) { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -SkipRODC:$SkipRODC.IsPresent -IncludeDomainControllers $IncludeDomainControllers -ExcludeDomainControllers $ExcludeDomainControllers } } Process { if ($ComputerName) { foreach ($Computer in $ComputerName) { Write-Verbose "Test-LDAP - Processing $Computer" $ServerName = ConvertTo-ComputerFQDN -Computer $Computer Test-LdapServer -ServerName $ServerName -Computer $Computer } } else { foreach ($Computer in $ForestInformation.ForestDomainControllers) { Write-Verbose "Test-LDAP - Processing $($Computer.HostName)" Test-LdapServer -ServerName $($Computer.HostName) -Computer $Computer.HostName -Advanced $Computer } } } } $ModuleFunctions = @{ActiveDirectory = @{'Add-ADACL' = '' 'Get-ADACL' = '' 'Get-ADACLOwner' = '' 'Get-WinADBitlockerLapsSummary' = '' 'Get-WinADDFSHealth' = '' 'Get-WinADDiagnostics' = '' 'Get-WinADDomain' = '' 'Get-WinADDuplicateObject' = 'Get-WinADForestObjectsConflict' 'Get-WinADForestOptionalFeatures' = '' 'Get-WinADForestReplication' = '' 'Get-WinADForestRoles' = 'Get-WinADRoles', 'Get-WinADDomainRoles' 'Get-WinADForestSchemaProperties' = '' 'Get-WinADForestSites' = '' 'Get-WinADGPOMissingPermissions' = '' 'Get-WinADGPOSysvolFolders' = '' 'Get-WinADLastBackup' = '' 'Get-WinADLDAPBindingsSummary' = '' 'Get-WinADLMSettings' = '' 'Get-WinADObject' = '' 'Get-WinADPrivilegedObjects' = 'Get-WinADPriviligedObjects' 'Get-WinADProxyAddresses' = '' 'Get-WinADSharePermission' = '' 'Get-WinADSiteConnections' = '' 'Get-WinADSiteLinks' = '' 'Get-WinADTomebstoneLifetime' = 'Get-WinADForestTomebstoneLifetime' 'Get-WinADTrustLegacy' = '' 'Get-WinADUserPrincipalName' = '' 'Get-WinADUsersForeignSecurityPrincipalList' = 'Get-WinADUsersFP' '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' = '' } DHCPServer = @{'Get-WinDHCP' = '' } } [Array] $FunctionsAll = 'Add-ADACL', 'Get-ADACL', 'Get-ADACLOwner', 'Get-DNSServerIP', 'Get-WinADACLConfiguration', 'Get-WinADACLForest', 'Get-WinADBitlockerLapsSummary', 'Get-WinADComputerACLLAPS', 'Get-WinADComputers', 'Get-WinADDelegatedAccounts', 'Get-WinADDFSHealth', 'Get-WinADDHCP', 'Get-WinADDiagnostics', 'Get-WinADDomain', 'Get-WinADDuplicateObject', 'Get-WinADForest', 'Get-WinADForestControllerInformation', 'Get-WinADForestOptionalFeatures', 'Get-WinADForestReplication', 'Get-WinADForestRoles', 'Get-WinADForestSchemaProperties', 'Get-WinADForestSites', 'Get-WinADForestSubnet', 'Get-WinADGroupMember', 'Get-WinADGroupMemberOf', 'Get-WinADLastBackup', 'Get-WinADLDAPBindingsSummary', 'Get-WinADLMSettings', 'Get-WinADObject', 'Get-WinADPrivilegedObjects', 'Get-WinADProxyAddresses', 'Get-WinADServiceAccount', 'Get-WinADSharePermission', 'Get-WinADSiteConnections', 'Get-WinADSiteLinks', 'Get-WinADTomebstoneLifetime', 'Get-WinADTrust', 'Get-WinADTrustLegacy', 'Get-WinADUserPrincipalName', 'Get-WinADUsers', 'Get-WinADUsersForeignSecurityPrincipalList', 'Get-WinADWellKnownFolders', 'Invoke-ADEssentials', 'Remove-ADACL', 'Remove-WinADDuplicateObject', 'Remove-WinADSharePermission', 'Rename-WinADUserPrincipalName', 'Repair-WinADACLConfigurationOwner', 'Repair-WinADEmailAddress', 'Repair-WinADForestControllerInformation', 'Set-ADACLOwner', 'Set-DnsServerIP', 'Set-WinADDiagnostics', 'Set-WinADReplication', 'Set-WinADReplicationConnections', 'Set-WinADShare', 'Set-WinADTombstoneLifetime', 'Show-WinADGroupMember', 'Show-WinADGroupMemberOf', 'Show-WinADTrust', 'Sync-DomainController', 'Test-ADDomainController', 'Test-ADRolesAvailability', 'Test-ADSiteLinks', 'Test-DNSNameServers', 'Test-FSMORolesAvailability', 'Test-LDAP' [Array] $AliasesAll = 'Get-WinADDomainRoles', 'Get-WinADForestObjectsConflict', 'Get-WinADForestTomebstoneLifetime', 'Get-WinADPriviligedObjects', 'Get-WinADRoles', 'Get-WinADSubnet', 'Get-WinADTrusts', 'Get-WinADUsersFP', 'Get-WinDNSServerIP', 'Set-WinDNSServerIP', 'Show-ADGroupMember', 'Show-ADGroupMemberOf', 'Show-ADTrust', 'Show-ADTrusts', 'Show-WinADTrusts' $AliasesToRemove = [System.Collections.Generic.List[string]]::new() $FunctionsToRemove = [System.Collections.Generic.List[string]]::new() foreach ($Module in $ModuleFunctions.Keys) { try { Import-Module -Name $Module -ErrorAction Stop } catch { foreach ($Function in $ModuleFunctions[$Module].Keys) { $FunctionsToRemove.Add($Function) $ModuleFunctions[$Module][$Function] | ForEach-Object { if ($_) { $AliasesToRemove.Add($_) } } } } } $FunctionsToLoad = foreach ($Function in $FunctionsAll) { if ($Function -notin $FunctionsToRemove) { $Function } } $AliasesToLoad = foreach ($Alias in $AliasesAll) { if ($Alias -notin $AliasesToRemove) { $Alias } } Export-ModuleMember -Function @($FunctionsToLoad) -Alias @($AliasesToLoad) # SIG # Begin signature block # MIIdWQYJKoZIhvcNAQcCoIIdSjCCHUYCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUkLs/cRTP1eBUBwh+3l+oUHM3 # 0DOgghhnMIIDtzCCAp+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 # 8jCCBP4wggPmoAMCAQICEA1CSuC+Ooj/YEAhzhQA8N0wDQYJKoZIhvcNAQELBQAw # cjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ # d3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVk # IElEIFRpbWVzdGFtcGluZyBDQTAeFw0yMTAxMDEwMDAwMDBaFw0zMTAxMDYwMDAw # MDBaMEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4G # A1UEAxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjEwggEiMA0GCSqGSIb3DQEBAQUA # A4IBDwAwggEKAoIBAQDC5mGEZ8WK9Q0IpEXKY2tR1zoRQr0KdXVNlLQMULUmEP4d # yG+RawyW5xpcSO9E5b+bYc0VkWJauP9nC5xj/TZqgfop+N0rcIXeAhjzeG28ffnH # bQk9vmp2h+mKvfiEXR52yeTGdnY6U9HR01o2j8aj4S8bOrdh1nPsTm0zinxdRS1L # sVDmQTo3VobckyON91Al6GTm3dOPL1e1hyDrDo4s1SPa9E14RuMDgzEpSlwMMYpK # jIjF9zBa+RSvFV9sQ0kJ/SYjU/aNY+gaq1uxHTDCm2mCtNv8VlS8H6GHq756Wwog # L0sJyZWnjbL61mOLTqVyHO6fegFz+BnW/g1JhL0BAgMBAAGjggG4MIIBtDAOBgNV # HQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD # CDBBBgNVHSAEOjA4MDYGCWCGSAGG/WwHATApMCcGCCsGAQUFBwIBFhtodHRwOi8v # d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHwYDVR0jBBgwFoAU9LbhIB3+Ka7S5GGlsqIl # ssgXNW4wHQYDVR0OBBYEFDZEho6kurBmvrwoLR1ENt3janq8MHEGA1UdHwRqMGgw # MqAwoC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMu # Y3JsMDKgMKAuhixodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVk # LXRzLmNybDCBhQYIKwYBBQUHAQEEeTB3MCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz # cC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKGQ2h0dHA6Ly9jYWNlcnRzLmRpZ2lj # ZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURUaW1lc3RhbXBpbmdDQS5jcnQw # DQYJKoZIhvcNAQELBQADggEBAEgc3LXpmiO85xrnIA6OZ0b9QnJRdAojR6OrktIl # xHBZvhSg5SeBpU0UFRkHefDRBMOG2Tu9/kQCZk3taaQP9rhwz2Lo9VFKeHk2eie3 # 8+dSn5On7UOee+e03UEiifuHokYDTvz0/rdkd2NfI1Jpg4L6GlPtkMyNoRdzDfTz # ZTlwS/Oc1np72gy8PTLQG8v1Yfx1CAB2vIEO+MDhXM/EEXLnG2RJ2CKadRVC9S0y # OIHa9GCiurRS+1zgYSQlT7LfySmoc0NR2r1j1h9bm/cuG08THfdKDXF+l7f0P4Tr # weOjSaH6zqe/Vs+6WXZhiV9+p7SOZ3j5NpjhyyjaW4emii8wggUwMIIEGKADAgEC # AhAECRgbX9W7ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVT # MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j # b20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEw # MjIxMjAwMDBaFw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV # BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEi # MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7 # RZmxOttE9X/lqJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p # 0WfTxvspJ8fTeyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj # 6YgsIJWuHEqHCN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grk # V7tKtel05iv+bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHy # DxL0xY4PwaLoLFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMB # AAGjggHNMIIByTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAT # BgNVHSUEDDAKBggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGG # GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2Nh # Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCB # gQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lD # ZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNl # cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgG # CmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu # Y29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1 # DlgwHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEL # BQADggEBAD7sDVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q # 3yBVN7Dh9tGSdQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/ # kLEbBw6RFfu6r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dc # IFzZcbEMj7uo+MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6 # dGRrsutmQ9qzsIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT # +hKUGIUukpHqaGxEMrJmoecYpJpkUe8wggUxMIIEGaADAgECAhAKoSXW1jIbfkHk # Bdo2l8IVMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE # aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMT # G0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcxMjAwMDBaFw0z # MTAxMDcxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0 # IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB # AQUAA4IBDwAwggEKAoIBAQC90DLuS82Pf92puoKZxTlUKFe2I0rEDgdFM1EQfdD5 # fU1ofue2oPSNs4jkl79jIZCYvxO8V9PD4X4I1moUADj3Lh477sym9jJZ/l9lP+Cb # 6+NGRwYaVX4LJ37AovWg4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT7l3ImgtU # 46gJcWvgzyIQD3XPcXJOCq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM/fDqR9mI # UF79Zm5WYScpiYRR5oLnRlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F0IQZchfx # FwbvPc3WTe8GQv2iUypPhR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHOMIIByjAd # BgNVHQ4EFgQU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAUReuir/SS # y4IxLVGLp6chnfNtyA8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMC # AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUF # BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6 # Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5j # cnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9E # aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwUAYDVR0gBEkw # RzA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj # ZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4IBAQBxlRLp # UYdWac3v3dp8qmN6s3jPBjdAhO9LhL/KzwMC/cWnww4gQiyvd/MrHwwhWiq3BTQd # aq6Z+CeiZr8JqmDfdqQ6kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVDBGiy23UC # 4HLHmNY8ZOUfSBAYX4k4YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3CzddWThZN+ # tpJn+1Nhiaj1a5bA9FhpDXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UBJrZspe6H # USHkWGCbugwtK22ixH67xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0e/VWMyIv # IjayS6JKldj1po5SMIIFPTCCBCWgAwIBAgIQBNXcH0jqydhSALrNmpsqpzANBgkq # hkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j # MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBT # SEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMDYyNjAwMDAwMFoX # DTIzMDcwNzEyMDAwMFowejELMAkGA1UEBhMCUEwxEjAQBgNVBAgMCcWabMSFc2tp # ZTERMA8GA1UEBxMIS2F0b3dpY2UxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlz # IEVWT1RFQzEhMB8GA1UEAwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIIBIjAN # BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7KB3iyBrhkLUbbFe9qxhKKPBYqD # Bqlnr3AtpZplkiVjpi9dMZCchSeT5ODsShPuZCIxJp5I86uf8ibo3vi2S9F9AlfF # jVye3dTz/9TmCuGH8JQt13ozf9niHecwKrstDVhVprgxi5v0XxY51c7zgMA2g1Ub # +3tii0vi/OpmKXdL2keNqJ2neQ5cYly/GsI8CREUEq9SZijbdA8VrRF3SoDdsWGf # 3tZZzO6nWn3TLYKQ5/bw5U445u/V80QSoykszHRivTj+H4s8ABiforhi0i76beA6 # Ea41zcH4zJuAp48B4UhjgRDNuq8IzLWK4dlvqrqCBHKqsnrF6BmBrv+BXQIDAQAB # o4IBxTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0O # BBYEFBixNSfoHFAgJk4JkDQLFLRNlJRmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE # DDAKBggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdp # Y2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2Ny # bDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUw # QzA3BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNl # cnQuY29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcw # AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8v # Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNp # Z25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAmr1s # z4lsLARi4wG1eg0B8fVJFowtect7SnJUrp6XRnUG0/GI1wXiLIeow1UPiI6uDMsR # XPHUF/+xjJw8SfIbwava2eXu7UoZKNh6dfgshcJmo0QNAJ5PIyy02/3fXjbUREHI # NrTCvPVbPmV6kx4Kpd7KJrCo7ED18H/XTqWJHXa8va3MYLrbJetXpaEPpb6zk+l8 # Rj9yG4jBVRhenUBUUj3CLaWDSBpOA/+sx8/XB9W9opYfYGb+1TmbCkhUg7TB3gD6 # o6ESJre+fcnZnPVAPESmstwsT17caZ0bn7zETKlNHbc1q+Em9kyBjaQRcEQoQQNp # ezQug9ufqExx6lHYDjGCBFwwggRYAgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYD # VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAv # BgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EC # EATV3B9I6snYUgC6zZqbKqcwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAI # oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB # CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFAurkEw0PA3UR9g0xdIT # tkzFPtdXMA0GCSqGSIb3DQEBAQUABIIBACA0ZmkB0bNPnHfuQGt9Ta9Ardlxe1YP # nkGciqFPU76ggaG2S9EKP0xvZcohsuQ79ID4Shd9JWAEjvzlWWIa8w+LZARbFbnf # Kn4E6DIIQJk+qvfeIoOgw1MOG1aySB65Jv/KLXzMS7eO/KoFPOs/7uLe2luD47wb # uqhKSkIecMDtihZWz60Inl0z6TjUtYocUEgYkWoEU2D9kmUj6PgLLrAfLk0lfIq+ # oWhJdra7GanA4+U2cwhtp1EZQZ5LZri4L+v7coQnQYzWa1SR8STuLyWB0xqFZjLL # NvLXhCdzijTR0rf6Qfu8onYYH3nHp9TRbsZWgf6LPfJvSORvBWzwFO2hggIwMIIC # LAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNV # BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8G # A1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQQIQ # DUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzEL # BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIxMDUzMDE2MDM1N1owLwYJKoZI # hvcNAQkEMSIEIPSGea72XVECDHb/vehqApMEjR0hDxo7CVVnDZ+9lJbcMA0GCSqG # SIb3DQEBAQUABIIBAF1Up0y5IaE5imYXxlUCjEMPo0om7ET7PYknn9RCpVFodLtf # XgUGbfTnJoylPbeX09bNFl5tjKSS4HNJfXoYdLFVD3yuctYOXdE2UWLsuT36Tyg7 # Wzr576owWiWsyVC9Cu64xfK1PhFEMSDbRUP2u1R+dFGwN2JQ/9U9KQdtrmit2Fqn # sQ2tSS9G7ws/6IhVi1GqcmjExkWrX4aiNmo0cY3M3JbGHhYsBKGNAUKoeEOKoart # zogYX23wSspvFnEHU+xR8JPmrLy3BTwtqPZUeTdUmTbC1o25GfxoFTHt4tOspbqJ # 2fZmSBJ4UTYk8u+u39ppVB2OtZ696Fzc9T2vR1E= # SIG # End signature block |