ADEssentials.psm1
function Convert-ADSchemaToGuid { <# .SYNOPSIS Converts name of schema properties to guids .DESCRIPTION Converts name of schema properties to guids .PARAMETER SchemaName Schema Name to convert to guid .PARAMETER All Get hashtable of all schema properties and their guids .PARAMETER Domain Domain to query. By default the current domain is used .PARAMETER RootDSE RootDSE to query. By default RootDSE is queried from the domain .PARAMETER AsString Return the guid as a string .EXAMPLE Convert-ADSchemaToGuid -SchemaName 'ms-Exch-MSO-Forward-Sync-Cookie' .EXAMPLE Convert-ADSchemaToGuid -SchemaName 'ms-Exch-MSO-Forward-Sync-Cookie' -AsString .NOTES General notes #> [CmdletBinding()] param( [string] $SchemaName, [string] $Domain, [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE, [switch] $AsString ) if (-not $Script:ADGuidMap -or -not $Script:ADGuidMapString) { if ($RootDSE) { $Script:RootDSE = $RootDSE } elseif (-not $Script:RootDSE) { if ($Domain) { $Script:RootDSE = Get-ADRootDSE -Server $Domain } else { $Script:RootDSE = Get-ADRootDSE } } $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Script:RootDSE.defaultNamingContext -ToDomainCN $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0] $Script:ADGuidMap = [ordered] @{ 'All' = [System.GUID]'00000000-0000-0000-0000-000000000000' } $Script:ADGuidMapString = [ordered] @{ 'All' = '00000000-0000-0000-0000-000000000000' } Write-Verbose "Convert-ADSchemaToGuid - Querying Schema from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:StandardRights) { $Script:StandardRights = Get-ADObject -SearchBase $Script:RootDSE.schemaNamingContext -LDAPFilter "(schemaidguid=*)" -Properties name, lDAPDisplayName, schemaIDGUID -Server $QueryServer -ErrorAction Stop | Select-Object name, lDAPDisplayName, schemaIDGUID } foreach ($Guid in $Script:StandardRights) { $Script:ADGuidMapString[$Guid.lDAPDisplayName] = ([System.GUID]$Guid.schemaIDGUID).Guid $Script:ADGuidMapString[$Guid.Name] = ([System.GUID]$Guid.schemaIDGUID).Guid $Script:ADGuidMap[$Guid.lDAPDisplayName] = ([System.GUID]$Guid.schemaIDGUID) $Script:ADGuidMap[$Guid.Name] = ([System.GUID]$Guid.schemaIDGUID) } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADSchemaToGuid - Querying Schema from $QueryServer took $TimeToExecute" Write-Verbose "Convert-ADSchemaToGuid - Querying Extended Rights from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:ExtendedRightsGuids) { $Script:ExtendedRightsGuids = Get-ADObject -SearchBase $Script:RootDSE.ConfigurationNamingContext -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties name, displayName, lDAPDisplayName, rightsGuid -Server $QueryServer -ErrorAction Stop | Select-Object name, displayName, lDAPDisplayName, rightsGuid } foreach ($Guid in $Script:ExtendedRightsGuids) { $Script:ADGuidMapString[$Guid.Name] = ([System.GUID]$Guid.RightsGuid).Guid $Script:ADGuidMapString[$Guid.DisplayName] = ([System.GUID]$Guid.RightsGuid).Guid $Script:ADGuidMap[$Guid.Name] = ([System.GUID]$Guid.RightsGuid) $Script:ADGuidMap[$Guid.DisplayName] = ([System.GUID]$Guid.RightsGuid) } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADSchemaToGuid - Querying Extended Rights from $QueryServer took $TimeToExecute" } if ($SchemaName) { if ($AsString) { return $Script:ADGuidMapString[$SchemaName] } else { return $Script:ADGuidMap[$SchemaName] } } else { if ($AsString) { $Script:ADGuidMapString } else { $Script:ADGuidMap } } } function Convert-CountryCodeToCountry { <# .SYNOPSIS Converts a country code to a country name, or when used with a switch to full culture information .DESCRIPTION Converts a country code to a country name, or when used with a switch to full culture information .PARAMETER CountryCode Country code .PARAMETER All Provide full culture information rather than just the country name .EXAMPLE Convert-CountryCodeToCountry -CountryCode 'PL' .EXAMPLE Convert-CountryCodeToCountry -CountryCode 'PL' -All .EXAMPLE $Test = Convert-CountryCodeToCountry $Test['PL']['Culture'] | fl $Test['PL']['RegionInformation'] .EXAMPLE Convert-CountryCodeToCountry -CountryCode 'PL' Convert-CountryCodeToCountry -CountryCode 'POL' .NOTES General notes #> [cmdletBinding()] param( [string] $CountryCode, [switch] $All ) if ($Script:QuickSearch) { if ($PSBoundParameters.ContainsKey('CountryCode')) { if ($All) { $Script:QuickSearch[$CountryCode] } else { $Script:QuickSearch[$CountryCode].RegionInformation.EnglishName } } else { $Script:QuickSearch } } else { $Script:QuickSearch = [ordered] @{} $AllCultures = [cultureinfo]::GetCultures([System.Globalization.CultureTypes]::SpecificCultures) foreach ($Culture in $AllCultures) { $RegionInformation = [System.Globalization.RegionInfo]::new($Culture) $Script:QuickSearch[$RegionInformation.TwoLetterISORegionName] = @{ 'Culture' = $Culture 'RegionInformation' = $RegionInformation } $Script:QuickSearch[$RegionInformation.ThreeLetterISORegionName] = @{ 'Culture' = $Culture 'RegionInformation' = $RegionInformation } } if ($PSBoundParameters.ContainsKey('CountryCode')) { if ($All) { $Script:QuickSearch[$CountryCode] } else { $Script:QuickSearch[$CountryCode].RegionInformation.EnglishName } } else { $Script:QuickSearch } } } 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-ExchangeEmail { <# .SYNOPSIS Function that helps converting Exchange email address list into readable, exportable format. .DESCRIPTION Function that helps converting Exchange email address list into readable, exportable format. .PARAMETER Emails List of emails as available in Exchange or Exchange Online, otherwise known as proxy addresses list .PARAMETER Separator .PARAMETER RemoveDuplicates .PARAMETER RemovePrefix .PARAMETER AddSeparator .EXAMPLE $Emails = @() $Emails += 'SIP:test@email.com' $Emails += 'SMTP:elo@maiu.com' $Emails += 'sip:elo@maiu.com' $Emails += 'Spo:dfte@sdsd.com' $Emails += 'SPO:myothertest@sco.com' Convert-ExchangeEmail -Emails $Emails -RemovePrefix -RemoveDuplicates -AddSeparator .NOTES General notes #> [CmdletBinding()] param( [string[]] $Emails, [string] $Separator = ', ', [switch] $RemoveDuplicates, [switch] $RemovePrefix, [switch] $AddSeparator ) if ($RemovePrefix) { $Emails = $Emails -replace 'smtp:', '' -replace 'sip:', '' -replace 'spo:', '' } if ($RemoveDuplicates) { $Emails = $Emails | Sort-Object -Unique } if ($AddSeparator) { $Emails = $Emails -join $Separator } return $Emails } function Convert-ExchangeRecipient { <# .SYNOPSIS Convert msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails to their respective name .DESCRIPTION Convert msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails to their respective name .PARAMETER RecipientTypeDetails RecipientTypeDetails to convert .PARAMETER RecipientType RecipientType to convert .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 .EXAMPLE Convert-ExchangeRecipient -msExchRemoteRecipientType 17 Convert-ExchangeRecipient -msExchRecipientDisplayType 17 Convert-ExchangeRecipient -msExchRecipientTypeDetails 17 .NOTES Based on: - https://granikos.eu/exchange-recipient-type-values/ - https://answers.microsoft.com/en-us/msoffice/forum/all/recipient-type-values/7c2620e5-9870-48ba-b5c2-7772c739c651 - https://www.undocumented-features.com/2020/05/06/every-last-msexchrecipientdisplaytype-and-msexchrecipienttypedetails-value/ #> [alias('Convert-ExchangeRecipientDetails')] [cmdletbinding(DefaultParameterSetName = 'msExchRecipientTypeDetails')] param( [parameter(ParameterSetName = 'msExchRecipientTypeDetails')][alias('RecipientTypeDetails')][string] $msExchRecipientTypeDetails, [parameter(ParameterSetName = 'msExchRecipientDisplayType')][alias('RecipientType')][string] $msExchRecipientDisplayType, [parameter(ParameterSetName = 'msExchRemoteRecipientType')][alias('RemoteRecipientType')][string] $msExchRemoteRecipientType, [parameter(ParameterSetName = 'msExchRecipientTypeDetails')] [parameter(ParameterSetName = 'msExchRecipientDisplayType')] [parameter(ParameterSetName = 'msExchRemoteRecipientType')] [switch] $All ) if ($PSBoundParameters.ContainsKey('msExchRecipientTypeDetails')) { $ListMsExchRecipientTypeDetails = [ordered] @{ '0' = 'None' '1' = 'UserMailbox' '2' = 'LinkedMailbox' '4' = 'SharedMailbox' '8' = 'LegacyMailbox' '16' = 'RoomMailbox' '32' = 'EquipmentMailbox' '64' = 'MailContact' '128' = 'MailUser' '256' = 'MailUniversalDistributionGroup' '512' = 'MailNonUniversalGroup' '1024' = 'MailUniversalSecurityGroup' '2048' = 'DynamicDistributionGroup' '4096' = 'PublicFolder' '8192' = 'SystemAttendantMailbox' '16384' = 'SystemMailbox' '32768' = 'MailForestContact' '65536' = 'User' '131072' = 'Contact' '262144' = 'UniversalDistributionGroup' '524288' = 'UniversalSecurityGroup' '1048576' = 'NonUniversalGroup' '2097152' = 'Disable User' '4194304' = 'MicrosoftExchange' '8388608' = 'ArbitrationMailbox' '16777216' = 'MailboxPlan' '33554432' = 'LinkedUser' '268435456' = 'RoomList' '536870912' = 'DiscoveryMailbox' '1073741824' = 'RoleGroup' '2147483648' = 'RemoteUserMailbox' '4294967296' = 'Computer' '8589934592' = 'RemoteRoomMailbox' '17179869184' = 'RemoteEquipmentMailbox' '34359738368' = 'RemoteSharedMailbox' '68719476736' = 'PublicFolderMailbox' '137438953472' = 'Team Mailbox' '274877906944' = 'RemoteTeamMailbox' '549755813888' = 'MonitoringMailbox' '1099511627776' = 'GroupMailbox' '2199023255552' = 'LinkedRoomMailbox' '4398046511104' = 'AuditLogMailbox' '8796093022208' = 'RemoteGroupMailbox' '17592186044416' = 'SchedulingMailbox' '35184372088832' = 'GuestMailUser' '70368744177664' = 'AuxAuditLogMailbox' '140737488355328' = 'SupervisoryReviewPolicyMailbox' } if ($All) { $ListMsExchRecipientTypeDetails } else { if ($null -ne $ListMsExchRecipientTypeDetails[$msExchRecipientTypeDetails]) { $ListMsExchRecipientTypeDetails[$msExchRecipientTypeDetails] } else { $msExchRecipientTypeDetails } } } elseif ($PSBoundParameters.ContainsKey('msExchRecipientDisplayType')) { $ListMsExchRecipientDisplayType = [ordered] @{ '0' = 'MailboxUser' '1' = 'DistributionGroup' '2' = 'PublicFolder' '3' = 'DynamicDistributionGroup' '4' = 'Organization' '5' = 'PrivateDistributionList' '6' = 'RemoteMailUser' '7' = 'ConferenceRoomMailbox' '8' = 'EquipmentMailbox' '10' = 'ArbitrationMailbox' '11' = 'MailboxPlan' '12' = 'LinkedUser' '15' = 'RoomList' '17' = 'Microsoft365Group' '-2147483642' = 'SyncedMailboxUser' '-2147483391' = 'SyncedUDGasUDG' '-2147483386' = 'SyncedUDGasContact' '-2147483130' = 'SyncedPublicFolder' '-2147482874' = 'SyncedDynamicDistributionGroup' '-2147482106' = 'SyncedRemoteMailUser' '-2147481850' = 'SyncedConferenceRoomMailbox' '-2147481594' = 'SyncedEquipmentMailbox' '-2147481343' = 'SyncedUSGasUDG' '-2147481338' = 'SyncedUSGasContact' '-1073741818' = 'ACLableSyncedMailboxUser' '-1073740282' = 'ACLableSyncedRemoteMailUser' '-1073739514' = 'ACLableSyncedUSGasContact' '-1073739511' = 'SyncedUSGasUSG' '1043741833' = 'SecurityDistributionGroup' '1073739511' = 'SyncedUSGasUSG' '1073739514' = 'ACLableSyncedUSGasContact' '1073741824' = 'ACLableMailboxUser' '1073741830' = 'ACLableRemoteMailUser' } if ($All) { $ListMsExchRecipientDisplayType } else { if ($null -ne $ListMsExchRecipientDisplayType[$msExchRecipientDisplayType]) { $ListMsExchRecipientDisplayType[$msExchRecipientDisplayType] } else { $msExchRecipientDisplayType } } } elseif ($PSBoundParameters.ContainsKey('msExchRemoteRecipientType')) { $ListMsExchRemoteRecipientType = [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' } if ($All) { $ListMsExchRemoteRecipientType } else { if ($null -ne $ListMsExchRemoteRecipientType[$msExchRemoteRecipientType]) { $ListMsExchRemoteRecipientType[$msExchRemoteRecipientType] } else { $msExchRemoteRecipientType } } } } function ConvertFrom-DistinguishedName { <# .SYNOPSIS Converts a Distinguished Name to CN, OU, Multiple OUs or DC .DESCRIPTION Converts a Distinguished Name to CN, OU, Multiple OUs or DC .PARAMETER DistinguishedName Distinguished Name to convert .PARAMETER ToOrganizationalUnit Converts DistinguishedName to Organizational Unit .PARAMETER ToDC Converts DistinguishedName to DC .PARAMETER ToDomainCN Converts DistinguishedName to Domain Canonical Name (CN) .PARAMETER ToCanonicalName Converts DistinguishedName to Canonical Name .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 .EXAMPLE ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit -IncludeParent Output: OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit Output: OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE $Con = @( 'CN=Windows Authorization Access Group,CN=Builtin,DC=ad,DC=evotec,DC=xyz' 'CN=Mmm,DC=elo,CN=nee,DC=RootDNSServers,CN=MicrosoftDNS,CN=System,DC=ad,DC=evotec,DC=xyz' 'CN=e6d5fd00-385d-4e65-b02d-9da3493ed850,CN=Operations,CN=DomainUpdates,CN=System,DC=ad,DC=evotec,DC=xyz' 'OU=Domain Controllers,DC=ad,DC=evotec,DC=pl' 'OU=Microsoft Exchange Security Groups,DC=ad,DC=evotec,DC=xyz' ) ConvertFrom-DistinguishedName -DistinguishedName $Con -ToLastName Output: Windows Authorization Access Group Mmm e6d5fd00-385d-4e65-b02d-9da3493ed850 Domain Controllers Microsoft Exchange Security Groups .EXAMPLEE ConvertFrom-DistinguishedName -DistinguishedName 'DC=ad,DC=evotec,DC=xyz' -ToCanonicalName ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName ConvertFrom-DistinguishedName -DistinguishedName 'CN=test,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName Output: ad.evotec.xyz ad.evotec.xyz\Production\Users ad.evotec.xyz\Production\Users\test .NOTES General notes #> [CmdletBinding(DefaultParameterSetName = 'Default')] param( [Parameter(ParameterSetName = 'ToOrganizationalUnit')] [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')] [Parameter(ParameterSetName = 'ToDC')] [Parameter(ParameterSetName = 'ToDomainCN')] [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'ToLastName')] [Parameter(ParameterSetName = 'ToCanonicalName')] [alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName, [Parameter(ParameterSetName = 'ToOrganizationalUnit')][switch] $ToOrganizationalUnit, [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][alias('ToMultipleOU')][switch] $ToMultipleOrganizationalUnit, [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][switch] $IncludeParent, [Parameter(ParameterSetName = 'ToDC')][switch] $ToDC, [Parameter(ParameterSetName = 'ToDomainCN')][switch] $ToDomainCN, [Parameter(ParameterSetName = 'ToLastName')][switch] $ToLastName, [Parameter(ParameterSetName = 'ToCanonicalName')][switch] $ToCanonicalName ) Process { foreach ($Distinguished in $DistinguishedName) { if ($ToDomainCN) { $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' $CN = $DN -replace ',DC=', '.' -replace "DC=" if ($CN) { $CN } } elseif ($ToOrganizationalUnit) { $Value = [Regex]::Match($Distinguished, '(?=OU=)(.*\n?)(?<=.)').Value if ($Value) { $Value } } elseif ($ToMultipleOrganizationalUnit) { if ($IncludeParent) { $Distinguished } while ($true) { $Distinguished = $Distinguished -replace '^.+?,(?=..=)' if ($Distinguished -match '^DC=') { break } $Distinguished } } elseif ($ToDC) { $Value = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' if ($Value) { $Value } } elseif ($ToLastName) { $NewDN = $Distinguished -split ",DC=" if ($NewDN[0].Contains(",OU=")) { [Array] $ChangedDN = $NewDN[0] -split ",OU=" } elseif ($NewDN[0].Contains(",CN=")) { [Array] $ChangedDN = $NewDN[0] -split ",CN=" } else { [Array] $ChangedDN = $NewDN[0] } if ($ChangedDN[0].StartsWith('CN=')) { $ChangedDN[0] -replace 'CN=', '' } else { $ChangedDN[0] -replace 'OU=', '' } } elseif ($ToCanonicalName) { $Domain = $null $Rest = $null foreach ($O in $Distinguished -split '(?<!\\),') { if ($O -match '^DC=') { $Domain += $O.Substring(3) + '.' } else { $Rest = $O.Substring(3) + '\' + $Rest } } if ($Domain -and $Rest) { $Domain.Trim('.') + '\' + ($Rest.TrimEnd('\') -replace '\\,', ',') } elseif ($Domain) { $Domain.Trim('.') } elseif ($Rest) { $Rest.TrimEnd('\') -replace '\\,', ',' } } else { $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$' $Found = $Distinguished -match $Regex if ($Found) { $Matches.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\LOCAL 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-568' = [PSCustomObject] @{ Name = 'BUILTIN\IIS_IUSRS' SID = 'S-1-5-32-568' 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 = '' } 'S-1-5-90-0' = [PSCustomObject] @{ Name = 'Window Manager\Window Manager Group' SID = 'S-1-5-90-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' = [PSCustomObject] @{ Name = 'NT SERVICE\WdiServiceHost' SID = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-3880718306-3832830129-1677859214-2598158968-1052248003' = [PSCustomObject] @{ Name = 'NT SERVICE\MSSQLSERVER' SID = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLSERVERAGENT' SID = 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLTELEMETRY' SID = 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451' = [PSCustomObject] @{ Name = 'NT SERVICE\ADSync' SID = 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083' = [PSCustomObject] @{ Name = 'NT Service\himds' SID = 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083' 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 { if (-not $Script:LocalComputerSID) { $Script:LocalComputerSID = Get-LocalComputerSid } try { if ($S.Length -le 18) { $Type = 'NotAdministrative' $Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value [PSCustomObject] @{ Name = $Name SID = $S DomainName = '' Type = $Type Error = '' } } else { 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 = if ($S -like "$Script:LocalComputerSID*") { '' } else { (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 .PARAMETER Force Allows to clear cache, useful when you want to force refresh .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, [switch] $Force ) Begin { if (-not $Script:GlobalCacheSidConvert -or $Force) { $Script:GlobalCacheSidConvert = @{ 'NT AUTHORITY\SYSTEM' = [PSCustomObject] @{ Name = 'BUILTIN\Administrators' SID = 'S-1-5-18' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'BUILTIN\Administrators' = [PSCustomObject] @{ Name = 'BUILTIN\Administrators' SID = 'S-1-5-32-544' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'BUILTIN\Users' = [PSCustomObject] @{ Name = 'BUILTIN\Users' SID = 'S-1-5-32-545' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Guests' = [PSCustomObject] @{ Name = 'BUILTIN\Guests' SID = 'S-1-5-32-546' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Power Users' = [PSCustomObject] @{ Name = 'BUILTIN\Power Users' SID = 'S-1-5-32-547' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Account Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Account Operators' SID = 'S-1-5-32-548' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Server Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Server Operators' SID = 'S-1-5-32-549' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Print Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Print Operators' SID = 'S-1-5-32-550' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Backup Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Backup Operators' SID = 'S-1-5-32-551' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Replicator' = [PSCustomObject] @{ Name = 'BUILTIN\Replicators' SID = 'S-1-5-32-552' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Pre-Windows 2000 Compatible Access' = [PSCustomObject] @{ Name = 'BUILTIN\Pre-Windows 2000 Compatible Access' SID = 'S-1-5-32-554' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Remote Desktop Users' = [PSCustomObject] @{ Name = 'BUILTIN\Remote Desktop Users' SID = 'S-1-5-32-555' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Network Configuration Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Network Configuration Operators' SID = 'S-1-5-32-556' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Incoming Forest Trust Builders' = [PSCustomObject] @{ Name = 'BUILTIN\Incoming Forest Trust Builders' SID = 'S-1-5-32-557' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Performance Monitor Users' = [PSCustomObject] @{ Name = 'BUILTIN\Performance Monitor Users' SID = 'S-1-5-32-558' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Performance Log Users' = [PSCustomObject] @{ Name = 'BUILTIN\Performance Log Users' SID = 'S-1-5-32-559' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Windows Authorization Access Group' = [PSCustomObject] @{ Name = 'BUILTIN\Windows Authorization Access Group' SID = 'S-1-5-32-560' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Terminal Server License Servers' = [PSCustomObject] @{ Name = 'BUILTIN\Terminal Server License Servers' SID = 'S-1-5-32-561' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Distributed COM Users' = [PSCustomObject] @{ Name = 'BUILTIN\Distributed COM Users' SID = 'S-1-5-32-562' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\IIS_IUSRS' = [PSCustomObject] @{ Name = 'BUILTIN\IIS_IUSRS' SID = 'S-1-5-32-568' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Cryptographic Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Cryptographic Operators' SID = 'S-1-5-32-569' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Event Log Readers' = [PSCustomObject] @{ Name = 'BUILTIN\Event Log Readers' SID = 'S-1-5-32-573' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Certificate Service DCOM Access' = [PSCustomObject] @{ Name = 'BUILTIN\Certificate Service DCOM Access' SID = 'S-1-5-32-574' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\RDS Remote Access Servers' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Remote Access Servers' SID = 'S-1-5-32-575' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\RDS Endpoint Servers' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Endpoint Servers' SID = 'S-1-5-32-576' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\RDS Management Servers' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Management Servers' SID = 'S-1-5-32-577' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Hyper-V Administrators' = [PSCustomObject] @{ Name = 'BUILTIN\Hyper-V Administrators' SID = 'S-1-5-32-578' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Access Control Assistance Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Access Control Assistance Operators' SID = 'S-1-5-32-579' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Remote Management Users' = [PSCustomObject] @{ Name = 'BUILTIN\Remote Management Users' SID = 'S-1-5-32-580' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'Window Manager\Window Manager Group' = [PSCustomObject] @{ Name = 'Window Manager\Window Manager Group' SID = 'S-1-5-90-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\WdiServiceHost' = [PSCustomObject] @{ Name = 'NT SERVICE\WdiServiceHost' SID = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\MSSQLSERVER' = [PSCustomObject] @{ Name = 'NT SERVICE\MSSQLSERVER' SID = 'S-1-5-80-3880718306-3832830129-1677859214-2598158968-1052248003' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\SQLSERVERAGENT' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLSERVERAGENT' SID = 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\SQLTELEMETRY' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLTELEMETRY' SID = 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\ADSync' = [PSCustomObject] @{ Name = 'NT SERVICE\ADSync' SID = 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT Service\himds' = [PSCustomObject] @{ Name = 'NT Service\himds' SID = 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083' DomainName = '' Type = 'WellKnownGroup' Error = '' } } } } 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, #[nullable[DateTime]] $StartTime, # can't use this just yet, some old code uses strings in StartTime/EndTime. #[nullable[DateTime]] $EndTime, # After that's fixed will change this. [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 .EXAMPLE $Registry = Get-PSRegistry -ComputerName 'AD1' -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' ConvertTo-OperatingSystem -OperatingSystem $Registry.ProductName -OperatingSystemVersion $Registry.CurrentBuildNumber .NOTES General notes #> [CmdletBinding()] param( [string] $OperatingSystem, [string] $OperatingSystemVersion ) if ($OperatingSystem -like 'Windows 10*' -or $OperatingSystem -like 'Windows 11*') { $Systems = @{ '10.0 (22621)' = 'Windows 11 22H2' '10.0 (22000)' = 'Windows 11 21H2' '10.0 (19045)' = 'Windows 10 22H2' '10.0 (19044)' = 'Windows 10 21H2' '10.0 (19043)' = 'Windows 10 21H1' '10.0 (19042)' = 'Windows 10 20H2' '10.0 (19041)' = 'Windows 10 2004' '10.0 (18898)' = 'Windows 10 Insider Preview' '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.22621' = 'Windows 11 22H2' '10.0.22000' = 'Windows 11 21H2' '10.0.19045' = 'Windows 10 22H2' '10.0.19044' = 'Windows 10 21H2' '10.0.19043' = 'Windows 10 21H1' '10.0.19042' = 'Windows 10 20H2' '10.0.19041' = 'Windows 10 2004' '10.0.18898' = 'Windows 10 Insider Preview' '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" '22621' = 'Windows 11 22H2' '22000' = 'Windows 11 21H2' '19045' = 'Windows 10 22H2' '19044' = 'Windows 10 21H2' '19043' = 'Windows 10 21H1' '19042' = 'Windows 10 20H2' '19041' = 'Windows 10 2004' '18898' = 'Windows 10 Insider Preview' '18363' = "Windows 10 1909" '18362' = "Windows 10 1903" '17763' = "Windows 10 1809" '17134' = "Windows 10 1803" '16299' = "Windows 10 1709" '15063' = "Windows 10 1703" '14393' = "Windows 10 1607" '10586' = "Windows 10 1511" '10240' = "Windows 10 1507" } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystemVersion } } elseif ($OperatingSystem -like 'Windows Server*') { $Systems = @{ '10.0 (20348)' = 'Windows Server 2022' '10.0 (19042)' = 'Windows Server 2019 20H2' '10.0 (19041)' = 'Windows Server 2019 2004' '10.0 (18363)' = 'Windows Server 2019 1909' '10.0 (18362)' = "Windows Server 2019 1903" '10.0 (17763)' = "Windows Server 2019 1809" '10.0 (17134)' = "Windows Server 2016 1803" '10.0 (14393)' = "Windows Server 2016 1607" '6.3 (9600)' = 'Windows Server 2012 R2' '6.1 (7601)' = 'Windows Server 2008 R2' '5.2 (3790)' = 'Windows Server 2003' '10.0.20348' = 'Windows Server 2022' '10.0.19042' = 'Windows Server 2019 20H2' '10.0.19041' = 'Windows Server 2019 2004' '10.0.18363' = 'Windows Server 2019 1909' '10.0.18362' = "Windows Server 2019 1903" '10.0.17763' = "Windows Server 2019 1809" '10.0.17134' = "Windows Server 2016 1803" '10.0.14393' = "Windows Server 2016 1607" '6.3.9600' = 'Windows Server 2012 R2' '6.1.7601' = 'Windows Server 2008 R2' '5.2.3790' = 'Windows Server 2003' '20348' = 'Windows Server 2022' '19042' = 'Windows Server 2019 20H2' '19041' = 'Windows Server 2019 2004' '18363' = 'Windows Server 2019 1909' '18362' = "Windows Server 2019 1903" '17763' = "Windows Server 2019 1809" '17134' = "Windows Server 2016 1803" '14393' = "Windows Server 2016 1607" '9600' = 'Windows Server 2012 R2' '7601' = 'Windows Server 2008 R2' '3790' = 'Windows Server 2003' } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystemVersion } } 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 { <# .SYNOPSIS Copies dictionary/hashtable .DESCRIPTION Copies dictionary uusing PS Serializer. Replaces usage of BinnaryFormatter due to no support in PS 7.4 .PARAMETER Dictionary Dictionary to copy .EXAMPLE $Test = [ordered] @{ Test = 'Test' Test1 = @{ Test2 = 'Test2' Test3 = @{ Test4 = 'Test4' } } Test2 = @( "1", "2", "3" ) Test3 = [PSCustomObject] @{ Test4 = 'Test4' Test5 = 'Test5' } } $New1 = Copy-Dictionary -Dictionary $Test $New1 .NOTES #> [alias('Copy-Hashtable', 'Copy-OrderedHashtable')] [cmdletbinding()] param( [System.Collections.IDictionary] $Dictionary ) $clone = [System.Management.Automation.PSSerializer]::Serialize($Dictionary, [int32]::MaxValue) return [System.Management.Automation.PSSerializer]::Deserialize($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 parametDer 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. .PARAMETER NameSpace Specifies the namespace for the CIM operation. The default namespace is root\cimv2. You can use tab completion to browse the list of namespaces, because PowerShell gets a list of namespaces from the local WMI server to provide a list of namespaces. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. .EXAMPLE Get-CimData -Class 'win32_bios' -ComputerName AD1,EVOWIN .EXAMPLE Get-CimData -Class 'win32_bios' .EXAMPLE 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', [pscredential] $Credential, [string[]] $Properties = '*' ) $ExcludeProperties = 'CimClass', 'CimInstanceProperties', 'CimSystemProperties', 'SystemCreationClassName', 'CreationClassName' [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName $CimObject = @( [string[]] $PropertiesOnly = $Properties | Where-Object { $_ -ne 'PSComputerName' } $Computers = $ComputersSplit[1] if ($Computers.Count -gt 0) { if ($Protocol -eq 'Default' -and $null -eq $Credential) { 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 $newCimSessionSplat = @{ ComputerName = $Computers SessionOption = $Option ErrorAction = 'SilentlyContinue' } if ($Credential) { $newCimSessionSplat['Credential'] = $Credential } $Session = New-CimSession @newCimSessionSplat -Verbose:$false if ($Session) { Try { $Info = Get-CimInstance -ClassName $Class -CimSession $Session -ErrorAction Stop -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties } catch { Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)" } try { $null = Remove-CimSession -CimSession $Session -ErrorAction SilentlyContinue } catch { Write-Warning -Message "Get-CimData - Failed to remove CimSession $($Session). Failed with errror: $($E.Exception.Message)" } $Info } else { Write-Warning -Message "Get-CimData - Failed to create CimSession for $($Computers). Problem with credentials?" } } foreach ($E in $ErrorsToProcess) { Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)" } } else { $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 [io.path]::Combine([System.IO.Path]::GetTempPath(), "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$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 { <# .SYNOPSIS Gets one or more releases from GitHub repository .DESCRIPTION Gets one or more releases from GitHub repository .PARAMETER Url Url to github repository .EXAMPLE Get-GitHubLatestRelease -Url "https://api.github.com/repos/evotecit/Testimo/releases" | Format-Table .NOTES General notes #> [CmdLetBinding()] param( [parameter(Mandatory)][alias('ReleasesUrl')][uri] $Url ) $ProgressPreference = 'SilentlyContinue' $Responds = Test-Connection -ComputerName $URl.Host -Quiet -Count 1 if ($Responds) { 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 } } } else { [PSCustomObject] @{ PublishDate = $null CreatedDate = $null PreRelease = $null Version = $null Tag = $null Branch = $null Errors = "No connection (ping) to $($Url.Host)" } } $ProgressPreference = 'Continue' } function Get-IPAddressRangeInformation { <# .SYNOPSIS Provides information about IP Address range .DESCRIPTION Provides information about IP Address range .PARAMETER Network Network in form of IP/NetworkLength (e.g. 10.2.10.0/24') .PARAMETER IPAddress IP Address to use .PARAMETER NetworkLength Network length to use .PARAMETER CIDRObject CIDRObject to use .EXAMPLE $CidrObject = @{ Ip = '10.2.10.0' NetworkLength = 24 } Get-IPAddressRangeInformation -CIDRObject $CidrObject | Format-Table .EXAMPLE Get-IPAddressRangeInformation -Network '10.2.10.0/24' | Format-Table .EXAMPLE Get-IPAddressRangeInformation -IPAddress '10.2.10.0' -NetworkLength 24 | Format-Table .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'Network')] param( [Parameter(ParameterSetName = 'Network', Mandatory)][string] $Network, [Parameter(ParameterSetName = 'IPAddress', Mandatory)][string] $IPAddress, [Parameter(ParameterSetName = 'IPAddress', Mandatory)][int] $NetworkLength, [Parameter(ParameterSetName = 'CIDR', Mandatory)][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)' if ($Network) { $CIDRObject = @{ Ip = $Network.Split('/')[0] NetworkLength = $Network.Split('/')[1] } } elseif ($IPAddress -and $NetworkLength) { $CIDRObject = @{ Ip = $IPAddress NetworkLength = $NetworkLength } } elseif ($CIDRObject) { } else { Write-Error "Get-IPAddressRangeInformation - Invalid parameters specified" return } $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-ProtocolDefaults { <# .SYNOPSIS Gets a list of default settings for SSL/TLS protocols .DESCRIPTION Gets a list of default settings for SSL/TLS protocols .PARAMETER WindowsVersion Windows Version to search for .PARAMETER AsList If true, returns a list of protocol names for all Windows Versions, otherwise returns a single entry for the specified Windows Version .EXAMPLE Get-ProtocolDefaults -AsList | Format-Table .EXAMPLE Get-ProtocolDefaults -WindowsVersion 'Windows 10 1809' | Format-Table .NOTES Based on: https://docs.microsoft.com/en-us/windows/win32/secauthn/protocols-in-tls-ssl--schannel-ssp- According to this https://github.com/MicrosoftDocs/windowsserverdocs/issues/2783 SCHANNEL service requires direct enablement so the list is kind of half useful #> [cmdletbinding(DefaultParameterSetName = 'WindowsVersion')] param( [Parameter(Mandatory, ParameterSetName = 'WindowsVersion')][string] $WindowsVersion, [Parameter(Mandatory, ParameterSetName = 'AsList')][switch] $AsList ) $Defaults = [ordered] @{ 'Windows Server 2022' = [ordered] @{ 'Version' = 'Windows Server 2022' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Enabled' 'TLS13Server' = 'Enabled' } 'Windows Server 2019 20H2' = [ordered] @{ 'Version' = 'Windows Server 2019 20H2' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2019 2004' = [ordered] @{ 'Version' = 'Windows Server 2019 2004' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2019 1909' = [ordered] @{ 'Version' = 'Windows Server 2019 1909' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows Server 2019 1903" = [ordered] @{ 'Version' = 'Windows Server 2019 1903' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows Server 2019 1809" = [ordered] @{ 'Version' = 'Windows Server 2019 1809' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows Server 2016 1803" = [ordered] @{ 'Version' = 'Windows Server 2016 1803' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows Server 2016 1607" = [ordered] @{ 'Version' = 'Windows Server 2019 1607' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2012 R2' = [ordered] @{ 'Version' = 'Windows Server 2012 R2' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Disabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2012' = [ordered] @{ 'Version' = 'Windows Server 2012' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Disabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2008 R2' = [ordered] @{ 'Version' = 'Windows Server 2008 R2' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Enabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Disabled' 'TLS11Server' = 'Disabled' 'TLS12Client' = 'Disabled' 'TLS12Server' = 'Disabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2008' = [ordered] @{ 'Version' = 'Windows Server 2008' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Enabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Disabled' 'TLS11Server' = 'Disabled' 'TLS12Client' = 'Disabled' 'TLS12Server' = 'Disabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows 11 21H2' = [ordered] @{ 'Version' = 'Windows 11 21H2' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Enabled' 'TLS13Server' = 'Enabled' } 'Windows 10 21H1' = [ordered] @{ 'Version' = 'Windows 10 21H1' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows 10 20H2' = [ordered] @{ 'Version' = 'Windows 10 20H2' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows 10 2004' = [ordered] @{ 'Version' = 'Windows 10 2004' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows 10 Insider Preview' = [ordered] @{ 'Version' = 'Windows 10 Insider Preview' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1909" = [ordered] @{ 'Version' = 'Windows 10 1909' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1903" = [ordered] @{ 'Version' = 'Windows 10 1903' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1809" = [ordered] @{ 'Version' = 'Windows 10 1809' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1803" = [ordered] @{ 'Version' = 'Windows 10 1803' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1709" = [ordered] @{ 'Version' = 'Windows 10 1709' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1703" = [ordered] @{ 'Version' = 'Windows 10 1703' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1607" = [ordered] @{ 'Version' = 'Windows 10 1607' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1511" = [ordered] @{ 'Version' = 'Windows 10 1511' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Disabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1507" = [ordered] @{ 'Version' = 'Windows 10 1507' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Disabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } } if ($AsList) { foreach ($Key in $Defaults.Keys) { [PSCustomObject] $Defaults[$Key] } } else { if ($Defaults[$WindowsVersion]) { $Defaults[$WindowsVersion] } else { [ordered] @{ 'Version' = 'Unknown' 'PCT10' = 'Unknown' 'SSL2Client' = 'Unknown' 'SSL2Server' = 'Unknown' 'SSL3Client' = 'Unknown' 'SSL3Server' = 'Unknown' 'TLS10Client' = 'Unknown' 'TLS10Server' = 'Unknown' 'TLS11Client' = 'Unknown' 'TLS11Server' = 'Unknown' 'TLS12Client' = 'Unknown' 'TLS12Server' = 'Unknown' 'TLS13Client' = 'Unknown' 'TLS13Server' = 'Unknown' } } } } function Get-PSRegistry { <# .SYNOPSIS Get registry key values. .DESCRIPTION Get registry key values. .PARAMETER RegistryPath The registry path to get the values from. .PARAMETER ComputerName The computer to get the values from. If not specified, the local computer is used. .PARAMETER ExpandEnvironmentNames Expand environment names in the registry value. By default it doesn't do that. If you want to expand environment names, use this parameter. .EXAMPLE Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -ComputerName AD1 .EXAMPLE Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' .EXAMPLE Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\DFSR\Parameters" -ComputerName AD1,AD2,AD3 | ft -AutoSize .EXAMPLE Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Directory Service' .EXAMPLE Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Windows PowerShell' | Format-Table -AutoSize .EXAMPLE Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Directory Service' -ComputerName AD1 -Advanced .EXAMPLE Get-PSRegistry -RegistryPath "HKLM:\Software\Microsoft\Powershell\1\Shellids\Microsoft.Powershell\" .EXAMPLE # Get default key and it's value Get-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -Key "" .EXAMPLE # Get default key and it's value (alternative) Get-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -DefaultKey .NOTES General notes #> [cmdletbinding()] param( [alias('Path')][string[]] $RegistryPath, [string[]] $ComputerName = $Env:COMPUTERNAME, [string] $Key, [switch] $Advanced, [switch] $DefaultKey, [switch] $ExpandEnvironmentNames, [Parameter(DontShow)][switch] $DoNotUnmount ) $Script:CurrentGetCount++ Get-PSRegistryDictionaries $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath [Array] $Computers = Get-ComputerSplit -ComputerName $ComputerName [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent if ($PSBoundParameters.ContainsKey("Key") -or $DefaultKey) { [Array] $RegistryValues = Get-PSSubRegistryTranslated -RegistryPath $RegistryTranslated -HiveDictionary $Script:HiveDictionary -Key $Key foreach ($Computer in $Computers[0]) { foreach ($R in $RegistryValues) { Get-PSSubRegistry -Registry $R -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } foreach ($Computer in $Computers[1]) { foreach ($R in $RegistryValues) { Get-PSSubRegistry -Registry $R -ComputerName $Computer -Remote -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } } else { [Array] $RegistryValues = Get-PSSubRegistryTranslated -RegistryPath $RegistryTranslated -HiveDictionary $Script:HiveDictionary foreach ($Computer in $Computers[0]) { foreach ($R in $RegistryValues) { Get-PSSubRegistryComplete -Registry $R -ComputerName $Computer -Advanced:$Advanced -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } foreach ($Computer in $Computers[1]) { foreach ($R in $RegistryValues) { Get-PSSubRegistryComplete -Registry $R -ComputerName $Computer -Remote -Advanced:$Advanced -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } } $Script:CurrentGetCount-- if ($Script:CurrentGetCount -eq 0) { if (-not $DoNotUnmount) { Unregister-MountedRegistry } } } 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 -Credential $Credential .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, [pscredential] $Credential ) try { if ($Credential) { $Forest = Get-ADForest -Credential $Credential } else { $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 if ($Credential) { $DC = Get-ADDomainController -Server $LocalServer.HostName[0] -Credential $Credential -Filter * -ErrorAction Stop } else { $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 { <# .SYNOPSIS Get details about Active Directory Forest, Domains and Domain Controllers in a single query .DESCRIPTION Get details about Active Directory Forest, Domains and Domain Controllers in a single query .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 Filter Filter for Get-ADDomainController .PARAMETER TestAvailability Check if Domain Controllers are available .PARAMETER Test Pick what to check for availability. Options are: All, Ping, WinRM, PortOpen, Ping+WinRM, Ping+PortOpen, WinRM+PortOpen. Default is All .PARAMETER Ports Ports to check for availability. Default is 135 .PARAMETER PortsTimeout Ports timeout for availability check. Default is 100 .PARAMETER PingCount How many pings to send. Default is 1 .PARAMETER PreferWritable Prefer writable domain controllers over read-only ones when returning Query Servers .PARAMETER Extended Return extended information about domains with NETBIOS names .EXAMPLE Get-WinADForestDetails | Format-Table .EXAMPLE Get-WinADForestDetails -Forest 'ad.evotec.xyz' | Format-Table .NOTES General notes #> [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] $PreferWritable, [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 ($Domain in $ForestInformation.Domains) { if ($IncludeDomains) { if ($Domain -in $IncludeDomains) { $Domain.ToLower() } continue } if ($Domain -notin $ExcludeDomains) { $Domain.ToLower() } } [Array] $DomainsActive = foreach ($Domain in $Findings['Forest'].Domains) { try { $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop -Writable:$PreferWritable.IsPresent $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 $Domain } [Array] $Findings['Domains'] = foreach ($Domain in $Findings['Domains']) { if ($Domain -notin $DomainsActive) { Write-Warning "Get-WinADForestDetails - Domain $Domain doesn't seem to be active (no DCs). Skipping." continue } $Domain } [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 } } } $DSAGuid = (Get-ADObject -Identity $S.NTDSSettingsObjectDN -Server $QueryServer).ObjectGUID $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 NTDSSettingsObjectDN = $S.NTDSSettingsObjectDN DsaGuid = $DSAGuid DsaGuidName = "$DSAGuid._msdcs.$($ForestInformation.RootDomain)" 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 = '' NTDSSettingsObjectDN = '' DsaGuid = '' DsaGuidName = '' 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 } if ($null -ne $Findings['DomainDomainControllers'][$Domain]) { [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 Convert-ADGuidToSchema { <# .SYNOPSIS Converts Guid to schema properties .DESCRIPTION Converts Guid to schema properties .PARAMETER Guid Guid to Convert to Schema Name .PARAMETER Domain Domain to query. By default the current domain is used .PARAMETER RootDSE RootDSE to query. By default RootDSE is queried from the domain .PARAMETER DisplayName Return the schema name by display name. By default it returns as Name .EXAMPLE $T2 = '570b9266-bbb3-4fad-a712-d2e3fedc34dd' $T = [guid] '570b9266-bbb3-4fad-a712-d2e3fedc34dd' Convert-ADGuidToSchema -Guid $T Convert-ADGuidToSchema -Guid $T2 .NOTES General notes #> [alias('Get-WinADDomainGUIDs', 'Get-WinADForestGUIDs')] [cmdletbinding()] param( [string] $Guid, [string] $Domain, [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE, [switch] $DisplayName ) if (-not $Script:ADSchemaMap -or -not $Script:ADSchemaMapDisplayName) { if ($RootDSE) { $Script:RootDSE = $RootDSE } elseif (-not $Script:RootDSE) { if ($Domain) { $Script:RootDSE = Get-ADRootDSE -Server $Domain } else { $Script:RootDSE = Get-ADRootDSE } } $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Script:RootDSE.defaultNamingContext -ToDomainCN $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0] $Script:ADSchemaMap = @{ } $Script:ADSchemaMapDisplayName = @{ } $Script:ADSchemaMapDisplayName['00000000-0000-0000-0000-000000000000'] = 'All' $Script:ADSchemaMap.Add('00000000-0000-0000-0000-000000000000', 'All') Write-Verbose "Convert-ADGuidToSchema - Querying Schema from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:StandardRights) { $Script:StandardRights = Get-ADObject -SearchBase $Script:RootDSE.schemaNamingContext -LDAPFilter "(schemaidguid=*)" -Properties name, lDAPDisplayName, schemaIDGUID -Server $QueryServer -ErrorAction Stop | Select-Object name, lDAPDisplayName, schemaIDGUID } foreach ($S in $Script:StandardRights) { $Script:ADSchemaMap["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.name $Script:ADSchemaMapDisplayName["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.lDAPDisplayName } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADGuidToSchema - Querying Schema from $QueryServer took $TimeToExecute" Write-Verbose "Convert-ADGuidToSchema - Querying Extended Rights from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:ExtendedRightsGuids) { $Script:ExtendedRightsGuids = Get-ADObject -SearchBase $Script:RootDSE.ConfigurationNamingContext -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties name, displayName, lDAPDisplayName, rightsGuid -Server $QueryServer -ErrorAction Stop | Select-Object name, displayName, lDAPDisplayName, rightsGuid } foreach ($S in $Script:ExtendedRightsGuids) { $Script:ADSchemaMap["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.name $Script:ADSchemaMapDisplayName["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.displayName } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADGuidToSchema - Querying Extended Rights from $QueryServer took $TimeToExecute" } if ($Guid) { if ($DisplayName) { $Script:ADSchemaMapDisplayName[$Guid] } else { $Script:ADSchemaMap[$Guid] } } else { if ($DisplayName) { $Script:ADSchemaMapDisplayName } else { $Script:ADSchemaMap } } } 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 { <# .SYNOPSIS Renames a name to a name without special chars. .DESCRIPTION Renames a name to a name without special chars. .PARAMETER String Provide a string to rename .EXAMPLE Rename-LatinCharacters -String 'PrzemysÅ‚aw KÅ‚ys' .EXAMPLE Rename-LatinCharacters -String 'PrzemysÅ‚aw' .NOTES General notes #> [alias('Remove-StringLatinCharacters')] [cmdletBinding()] 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 { <# .SYNOPSIS Sets/Updates registry entries locally and remotely using .NET methods. .DESCRIPTION Sets/Updates registry entries locally and remotely using .NET methods. If the registry path to key doesn't exists it will be created. .PARAMETER ComputerName The computer to run the command on. Defaults to local computer. .PARAMETER RegistryPath Registry Path to Update .PARAMETER Type Registry type to use. Options are: REG_SZ, REG_EXPAND_SZ, REG_BINARY, REG_DWORD, REG_MULTI_SZ, REG_QWORD, string, expandstring, binary, dword, multistring, qword .PARAMETER Key Registry key to set. If the path to registry key doesn't exists it will be created. .PARAMETER Value Registry value to set. .PARAMETER Suppress Suppresses the output of the command. By default the command outputs PSObject with the results of the operation. .EXAMPLE Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' -Type REG_DWORD -Key "16 LDAP Interface Events" -Value 2 -ComputerName AD1 .EXAMPLE Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' -Type REG_SZ -Key "LDAP Interface Events" -Value 'test' -ComputerName AD1 .EXAMPLE Set-PSRegistry -RegistryPath "HKCU:\\Tests" -Key "LimitBlankPass1wordUse" -Value "0" -Type REG_DWORD .EXAMPLE Set-PSRegistry -RegistryPath "HKCU:\\Tests\MoreTests\Tests1" -Key "LimitBlankPass1wordUse" -Value "0" -Type REG_DWORD .EXAMPLE # Setting default value $ValueData = [byte[]] @( 0, 1, 0, 0, 9, 0, 0, 0, 128, 0, 0, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 5, 0, 10, 0, 14, 0, 3, 0, 5, 0, 6, 0, 6, 0, 4, 0, 4, 0 ) Set-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -Key '' -Value $ValueData -Type 'NONE' .NOTES General notes #> [cmdletbinding(SupportsShouldProcess)] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [Parameter(Mandatory)][string] $RegistryPath, [Parameter(Mandatory)][ValidateSet('REG_SZ', 'REG_NONE', 'None', 'REG_EXPAND_SZ', 'REG_BINARY', 'REG_DWORD', 'REG_MULTI_SZ', 'REG_QWORD', 'string', 'binary', 'dword', 'qword', 'multistring', 'expandstring')][string] $Type, [Parameter()][string] $Key, [Parameter(Mandatory)][object] $Value, [switch] $Suppress ) Unregister-MountedRegistry Get-PSRegistryDictionaries [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary foreach ($Registry in $RegistryTranslated) { $RegistryValue = Get-PrivateRegistryTranslated -RegistryPath $Registry -HiveDictionary $Script:HiveDictionary -Key $Key -Value $Value -Type $Type -ReverseTypesDictionary $Script:ReverseTypesDictionary if ($RegistryValue.HiveKey) { foreach ($Computer in $ComputersSplit[0]) { Set-PrivateRegistry -RegistryValue $RegistryValue -Computer $Computer -Suppress:$Suppress.IsPresent -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference } foreach ($Computer in $ComputersSplit[1]) { Set-PrivateRegistry -RegistryValue $RegistryValue -Computer $Computer -Remote -Suppress:$Suppress.IsPresent -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference } } else { if ($PSBoundParameters.ErrorAction -eq 'Stop') { Unregister-MountedRegistry throw } else { Write-Warning "Set-PSRegistry - Setting registry to $Registry have failed. Couldn't translate HIVE." } } } Unregister-MountedRegistry } 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 delivering a lot of additional features for easier color options. .DESCRIPTION Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options. It provides: - Easy manipulation of colors, - Logging output to file (log) - Nice formatting options out of the box. - Ability to use aliases for parameters .PARAMETER Text Text to display on screen and write to log file if specified. Accepts an array of strings. .PARAMETER Color Color of the text. Accepts an array of colors. If more than one color is specified it will loop through colors for each string. If there are more strings than colors it will start from the beginning. Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White .PARAMETER BackGroundColor Color of the background. Accepts an array of colors. If more than one color is specified it will loop through colors for each string. If there are more strings than colors it will start from the beginning. Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White .PARAMETER StartTab Number of tabs to add before text. Default is 0. .PARAMETER LinesBefore Number of empty lines before text. Default is 0. .PARAMETER LinesAfter Number of empty lines after text. Default is 0. .PARAMETER StartSpaces Number of spaces to add before text. Default is 0. .PARAMETER LogFile Path to log file. If not specified no log file will be created. .PARAMETER DateTimeFormat Custom date and time format string. Default is yyyy-MM-dd HH:mm:ss .PARAMETER LogTime If set to $true it will add time to log file. Default is $true. .PARAMETER LogRetry Number of retries to write to log file, in case it can't write to it for some reason, before skipping. Default is 2. .PARAMETER Encoding Encoding of the log file. Default is Unicode. .PARAMETER ShowTime Switch to add time to console output. Default is not set. .PARAMETER NoNewLine Switch to not add new line at the end of the output. Default is not set. .PARAMETER NoConsoleOutput Switch to not output to console. Default all output goes to console. .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 Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow Write-Color -t "my text" -c yellow -b green Write-Color -text "my text" -c red .EXAMPLE Write-Color -Text "TestujÄ™ czy siÄ™ Å‚adnie zapisze, czy bÄ™dÄ… problemy" -Encoding unicode -LogFile 'C:\temp\testinggg.txt' -Color Red -NoConsoleOutput .NOTES Understanding Custom date and time format strings: https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings Project support: https://github.com/EvotecIT/PSWriteColor Original idea: Josh (https://stackoverflow.com/users/81769/josh) #> [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, [alias('HideConsole')][switch] $NoConsoleOutput ) if (-not $NoConsoleOutput) { $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 } } # Add empty line before if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewline } } # Add TABS before text if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewline } } # Add SPACES before text if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline } # Add Time before output if ($Text.Count -ne 0) { if ($Color.Count -ge $Text.Count) { # the real deal coloring 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 } # Support for no new line if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } } # Add empty line after } if ($Text.Count -and $LogFile) { # Save to file $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) { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Tried ($Retry/$LogRetry))" } 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-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-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 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 Copy-DictionaryManual { [CmdletBinding()] param( [System.Collections.IDictionary] $Dictionary ) $clone = [ordered] @{} 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 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-LocalComputerSid { <# .SYNOPSIS Get the SID of the local computer. .DESCRIPTION Get the SID of the local computer. .EXAMPLE Get-LocalComputerSid .NOTES General notes #> [cmdletBinding()] param() try { Add-Type -AssemblyName System.DirectoryServices.AccountManagement $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::Machine) $UserPrincipal = [System.DirectoryServices.AccountManagement.UserPrincipal]::new($PrincipalContext) $Searcher = [System.DirectoryServices.AccountManagement.PrincipalSearcher]::new() $Searcher.QueryFilter = $UserPrincipal $User = $Searcher.FindAll() foreach ($U in $User) { if ($U.Sid.Value -like "*-500") { return $U.Sid.Value.TrimEnd("-500") } } } catch { Write-Warning -Message "Get-LocalComputerSid - Error: $($_.Exception.Message)" } } function Get-PrivateRegistryTranslated { [cmdletBinding()] param( [Array] $RegistryPath, [System.Collections.IDictionary] $HiveDictionary, [System.Collections.IDictionary] $ReverseTypesDictionary, [Parameter()][ValidateSet('REG_SZ', 'REG_NONE', 'None', 'REG_EXPAND_SZ', 'REG_BINARY', 'REG_DWORD', 'REG_MULTI_SZ', 'REG_QWORD', 'string', 'binary', 'dword', 'qword', 'multistring', 'expandstring')][string] $Type, [Parameter()][string] $Key, [Parameter()][object] $Value ) foreach ($Registry in $RegistryPath) { if ($Registry -is [string]) { $Registry = $Registry.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\") } else { $Registry.RegistryPath = $Registry.RegistryPath.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\") } foreach ($Hive in $HiveDictionary.Keys) { if ($Registry -is [string] -and $Registry.StartsWith($Hive, [System.StringComparison]::CurrentCultureIgnoreCase)) { if ($Hive.Length -eq $Registry.Length) { [ordered] @{ HiveKey = $HiveDictionary[$Hive] SubKeyName = $null ValueKind = if ($Type) { [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) } else { $null } Key = $Key Value = $Value } } else { [ordered] @{ HiveKey = $HiveDictionary[$Hive] SubKeyName = $Registry.substring($Hive.Length + 1) ValueKind = if ($Type) { [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) } else { $null } Key = $Key Value = $Value } } break } elseif ($Registry -isnot [string] -and $Registry.RegistryPath.StartsWith($Hive, [System.StringComparison]::CurrentCultureIgnoreCase)) { if ($Hive.Length -eq $Registry.RegistryPath.Length) { [ordered] @{ ComputerName = $Registry.ComputerName HiveKey = $HiveDictionary[$Hive] SubKeyName = $null ValueKind = if ($Type) { [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) } else { $null } Key = $Key Value = $Value } } else { [ordered] @{ ComputerName = $Registry.ComputerName HiveKey = $HiveDictionary[$Hive] SubKeyName = $Registry.RegistryPath.substring($Hive.Length + 1) ValueKind = if ($Type) { [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) } else { $null } Key = $Key Value = $Value } } break } } } } function Get-PSConvertSpecialRegistry { [cmdletbinding()] param( [Array] $RegistryPath, [Array] $Computers, [System.Collections.IDictionary] $HiveDictionary, [switch] $ExpandEnvironmentNames ) $FixedPath = foreach ($R in $RegistryPath) { foreach ($DictionaryKey in $HiveDictionary.Keys) { $SplitParts = $R.Split("\") $FirstPart = $SplitParts[0] if ($FirstPart -eq $DictionaryKey) { if ($HiveDictionary[$DictionaryKey] -in 'All', 'All+Default', 'Default', 'AllDomain+Default', 'AllDomain', 'AllDomain+Other', 'AllDomain+Other+Default') { foreach ($Computer in $Computers) { $SubKeys = Get-PSRegistry -RegistryPath "HKEY_USERS" -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent -DoNotUnmount if ($SubKeys.PSSubKeys) { $RegistryKeys = ConvertTo-HKeyUser -SubKeys ($SubKeys.PSSubKeys | Sort-Object) -HiveDictionary $HiveDictionary -DictionaryKey $DictionaryKey -RegistryPath $R foreach ($S in $RegistryKeys) { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $S Error = $null ErrorMessage = $null } } } else { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $R Error = $true ErrorMessage = "Couldn't connect to $Computer to list HKEY_USERS" } } } } elseif ($FirstPart -in 'Users', 'HKEY_USERS', 'HKU' -and $SplitParts[1] -and $SplitParts[1] -like "Offline_*") { foreach ($Computer in $Computers) { $SubKeys = Get-PSRegistry -RegistryPath "HKEY_USERS" -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent -DoNotUnmount if ($SubKeys.PSSubKeys) { $RegistryKeys = ConvertTo-HKeyUser -SubKeys ($SubKeys.PSSubKeys + $SplitParts[1] | Sort-Object) -HiveDictionary $HiveDictionary -DictionaryKey $DictionaryKey -RegistryPath $R foreach ($S in $RegistryKeys) { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $S Error = $null ErrorMessage = $null } } } else { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $R Error = $true ErrorMessage = "Couldn't connect to $Computer to list HKEY_USERS" } } } } else { $R } break } } } $FixedPath } function Get-PSRegistryDictionaries { [cmdletBinding()] param() if ($Script:Dictionary) { return } $Script:Dictionary = @{ 'HKUAD:' = 'HKEY_ALL_USERS_DEFAULT' 'HKUA:' = 'HKEY_ALL_USERS' 'HKUD:' = 'HKEY_DEFAULT_USER' 'HKUDUD:' = 'HKEY_ALL_DOMAIN_USERS_DEFAULT' 'HKUDU:' = 'HKEY_ALL_DOMAIN_USERS' 'HKUDUO:' = 'HKEY_ALL_DOMAIN_USERS_OTHER' 'HKUDUDO:' = 'HKEY_ALL_DOMAIN_USERS_OTHER_DEFAULT' 'HKCR:' = 'HKEY_CLASSES_ROOT' 'HKCU:' = 'HKEY_CURRENT_USER' 'HKLM:' = 'HKEY_LOCAL_MACHINE' 'HKU:' = 'HKEY_USERS' 'HKCC:' = 'HKEY_CURRENT_CONFIG' 'HKDD:' = 'HKEY_DYN_DATA' 'HKPD:' = 'HKEY_PERFORMANCE_DATA' } $Script:HiveDictionary = [ordered] @{ 'HKEY_ALL_USERS_DEFAULT' = 'All+Default' 'HKUAD' = 'All+Default' 'HKEY_ALL_USERS' = 'All' 'HKUA' = 'All' 'HKEY_ALL_DOMAIN_USERS_DEFAULT' = 'AllDomain+Default' 'HKUDUD' = 'AllDomain+Default' 'HKEY_ALL_DOMAIN_USERS' = 'AllDomain' 'HKUDU' = 'AllDomain' 'HKEY_DEFAULT_USER' = 'Default' 'HKUD' = 'Default' 'HKEY_ALL_DOMAIN_USERS_OTHER' = 'AllDomain+Other' 'HKUDUO' = 'AllDomain+Other' 'HKUDUDO' = 'AllDomain+Other+Default' 'HKEY_ALL_DOMAIN_USERS_OTHER_DEFAULT' = 'AllDomain+Other+Default' 'HKEY_CLASSES_ROOT' = 'ClassesRoot' 'HKCR' = 'ClassesRoot' 'ClassesRoot' = 'ClassesRoot' 'HKCU' = 'CurrentUser' 'HKEY_CURRENT_USER' = 'CurrentUser' 'CurrentUser' = 'CurrentUser' 'HKLM' = 'LocalMachine' 'HKEY_LOCAL_MACHINE' = 'LocalMachine' 'LocalMachine' = 'LocalMachine' 'HKU' = 'Users' 'HKEY_USERS' = 'Users' 'Users' = 'Users' 'HKCC' = 'CurrentConfig' 'HKEY_CURRENT_CONFIG' = 'CurrentConfig' 'CurrentConfig' = 'CurrentConfig' 'HKDD' = 'DynData' 'HKEY_DYN_DATA' = 'DynData' 'DynData' = 'DynData' 'HKPD' = 'PerformanceData' 'HKEY_PERFORMANCE_DATA ' = 'PerformanceData' 'PerformanceData' = 'PerformanceData' } $Script:ReverseTypesDictionary = [ordered] @{ 'REG_SZ' = 'string' 'REG_NONE' = 'none' 'REG_EXPAND_SZ' = 'expandstring' 'REG_BINARY' = 'binary' 'REG_DWORD' = 'dword' 'REG_MULTI_SZ' = 'multistring' 'REG_QWORD' = 'qword' 'string' = 'string' 'expandstring' = 'expandstring' 'binary' = 'binary' 'dword' = 'dword' 'multistring' = 'multistring' 'qword' = 'qword' 'none' = 'none' } } function Get-PSSubRegistry { [cmdletBinding()] param( [System.Collections.IDictionary] $Registry, [string] $ComputerName, [switch] $Remote, [switch] $ExpandEnvironmentNames ) if ($Registry.ComputerName) { if ($Registry.ComputerName -ne $ComputerName) { return } } if (-not $Registry.Error) { try { if ($Remote) { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Registry.HiveKey, $ComputerName, 0 ) } else { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Registry.HiveKey, 0 ) } $PSConnection = $true $PSError = $null } catch { $PSConnection = $false $PSError = $($_.Exception.Message) } } else { $PSConnection = $false $PSError = $($Registry.ErrorMessage) } if ($PSError) { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $PSError PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = $null PSType = $null } } else { try { $SubKey = $BaseHive.OpenSubKey($Registry.SubKeyName, $false) if ($null -ne $SubKey) { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $false PSErrorMessage = $null PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($Registry.Key, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($Registry.Key) } PSType = $SubKey.GetValueKind($Registry.Key) } } else { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = "Registry path $($Registry.Registry) doesn't exists." PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = $null PSType = $null } } } catch { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $_.Exception.Message PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = $null PSType = $null } } } if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } } function Get-PSSubRegistryComplete { [cmdletBinding()] param( [System.Collections.IDictionary] $Registry, [string] $ComputerName, [switch] $Remote, [switch] $Advanced, [switch] $ExpandEnvironmentNames ) if ($Registry.ComputerName) { if ($Registry.ComputerName -ne $ComputerName) { return } } if (-not $Registry.Error) { try { if ($Remote) { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Registry.HiveKey, $ComputerName, 0 ) } else { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Registry.HiveKey, 0 ) } $PSConnection = $true $PSError = $null } catch { $PSConnection = $false $PSError = $($_.Exception.Message) } } else { $PSConnection = $false $PSError = $($Registry.ErrorMessage) } if ($PSError) { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $PSError PSSubKeys = $null PSPath = $Registry.Registry PSKey = $Registry.Key } } else { try { $SubKey = $BaseHive.OpenSubKey($Registry.SubKeyName, $false) if ($null -ne $SubKey) { $Object = [ordered] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $false PSErrorMessage = $null PSSubKeys = $SubKey.GetSubKeyNames() PSPath = $Registry.Registry } $Keys = $SubKey.GetValueNames() foreach ($K in $Keys) { if ($K -eq "") { if ($Advanced) { $Object['DefaultKey'] = [ordered] @{ Value = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($K) } Type = $SubKey.GetValueKind($K) } } else { $Object['DefaultKey'] = $SubKey.GetValue($K) } } else { if ($Advanced) { $Object[$K] = [ordered] @{ Value = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($K) } Type = $SubKey.GetValueKind($K) } } else { $Object[$K] = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($K) } } } } [PSCustomObject] $Object } else { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = "Registry path $($Registry.Registry) doesn't exists." PSSubKeys = $null PSPath = $Registry.Registry } } } catch { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $_.Exception.Message PSSubKeys = $null PSPath = $Registry.Registry } } } if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } } function Get-PSSubRegistryTranslated { [cmdletBinding()] param( [Array] $RegistryPath, [System.Collections.IDictionary] $HiveDictionary, [string] $Key ) foreach ($Registry in $RegistryPath) { if ($Registry -is [string]) { $Registry = $Registry.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\") $FirstPartSplit = $Registry -split "\\" $FirstPart = $FirstPartSplit[0] } else { $Registry.RegistryPath = $Registry.RegistryPath.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\") $FirstPartSplit = $Registry.RegistryPath -split "\\" $FirstPart = $FirstPartSplit[0] } foreach ($Hive in $HiveDictionary.Keys) { if ($Registry -is [string] -and $FirstPart -eq $Hive) { if ($Hive.Length -eq $Registry.Length) { [ordered] @{ Registry = $Registry HiveKey = $HiveDictionary[$Hive] SubKeyName = $null Key = if ($Key -eq "") { $null } else { $Key } Error = $null ErrorMessage = $null } } else { [ordered] @{ Registry = $Registry HiveKey = $HiveDictionary[$Hive] SubKeyName = $Registry.substring($Hive.Length + 1) Key = if ($Key -eq "") { $null } else { $Key } Error = $null ErrorMessage = $null } } break } elseif ($Registry -isnot [string] -and $FirstPart -eq $Hive) { if ($Hive.Length -eq $Registry.RegistryPath.Length) { [ordered] @{ ComputerName = $Registry.ComputerName Registry = $Registry.RegistryPath HiveKey = $HiveDictionary[$Hive] SubKeyName = $null Key = if ($Key -eq "") { $null } else { $Key } Error = $Registry.Error ErrorMessage = $Registry.ErrorMessage } } else { [ordered] @{ ComputerName = $Registry.ComputerName Registry = $Registry.RegistryPath HiveKey = $HiveDictionary[$Hive] SubKeyName = $Registry.RegistryPath.substring($Hive.Length + 1) Key = if ($Key -eq "") { $null } else { $Key } Error = $Registry.Error ErrorMessage = $Registry.ErrorMessage } } break } } } } function Resolve-PrivateRegistry { [CmdletBinding()] param( [alias('Path')][string[]] $RegistryPath ) foreach ($R in $RegistryPath) { $R = $R.Replace("\\", "\").Replace("\\", "\") If ($R.StartsWith("Users\.DEFAULT_USER") -or $R.StartsWith('HKEY_USERS\.DEFAULT_USER')) { $R = $R.Replace("Users\.DEFAULT_USER", "HKUD") $R.Replace('HKEY_USERS\.DEFAULT_USER', "HKUD") } elseif ($R -like '*:*') { $Found = $false foreach ($DictionaryKey in $Script:Dictionary.Keys) { $SplitParts = $R.Split("\") $FirstPart = $SplitParts[0] if ($FirstPart -eq $DictionaryKey) { $R -replace $DictionaryKey, $Script:Dictionary[$DictionaryKey] $Found = $true break } } if (-not $Found) { $R.Replace(":", "") } } else { $R } } } function Set-PrivateRegistry { [cmdletBinding(SupportsShouldProcess)] param( [System.Collections.IDictionary] $RegistryValue, [string] $Computer, [switch] $Remote, [switch] $Suppress ) Write-Verbose -Message "Set-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer" if ($RegistryValue.ComputerName) { if ($RegistryValue.ComputerName -ne $Computer) { return } } try { if ($Remote) { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryValue.HiveKey, $Computer, 0 ) } else { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryValue.HiveKey, 0 ) } $PSConnection = $true $PSError = $null } catch { $PSConnection = $false $PSError = $($_.Exception.Message) if ($PSBoundParameters.ErrorAction -eq 'Stop') { if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } throw } else { Write-Warning "Set-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))" } } if ($PSCmdlet.ShouldProcess($Computer, "Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind)")) { if ($PSError) { if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $true PSErrorMessage = $PSError Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key Value = $RegistryValue.Value Type = $RegistryValue.ValueKind } } } else { try { $SubKey = $BaseHive.OpenSubKey($RegistryValue.SubKeyName, $true) if (-not $SubKey) { $SubKeysSplit = $RegistryValue.SubKeyName.Split('\') $SubKey = $BaseHive.OpenSubKey($SubKeysSplit[0], $true) if (-not $SubKey) { $SubKey = $BaseHive.CreateSubKey($SubKeysSplit[0]) } $SubKey = $BaseHive.OpenSubKey($SubKeysSplit[0], $true) foreach ($S in $SubKeysSplit | Select-Object -Skip 1) { $SubKey = $SubKey.CreateSubKey($S) } } if ($RegistryValue.ValueKind -eq [Microsoft.Win32.RegistryValueKind]::MultiString) { $SubKey.SetValue($RegistryValue.Key, [string[]] $RegistryValue.Value, $RegistryValue.ValueKind) } elseif ($RegistryValue.ValueKind -in [Microsoft.Win32.RegistryValueKind]::None, [Microsoft.Win32.RegistryValueKind]::Binary) { $SubKey.SetValue($RegistryValue.Key, [byte[]] $RegistryValue.Value, $RegistryValue.ValueKind) } else { $SubKey.SetValue($RegistryValue.Key, $RegistryValue.Value, $RegistryValue.ValueKind) } if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $false PSErrorMessage = $null Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key Value = $RegistryValue.Value Type = $RegistryValue.ValueKind } } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } throw } else { Write-Warning "Set-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))" } if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $true PSErrorMessage = $_.Exception.Message Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key Value = $RegistryValue.Value Type = $RegistryValue.ValueKind } } } } } else { if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $true PSErrorMessage = if ($PSError) { $PSError } else { "WhatIf used - skipping registry setting" } Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key Value = $RegistryValue.Value Type = $RegistryValue.ValueKind } } } if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } } 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-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-WinRM { [CmdletBinding()] param ( [alias('Server')][string[]] $ComputerName ) $Output = foreach ($Computer in $ComputerName) { $Test = [PSCustomObject] @{ Output = $null Status = $null ComputerName = $Computer } try { $Test.Output = Test-WSMan -ComputerName $Computer -ErrorAction Stop $Test.Status = $true } catch { $Test.Status = $false } $Test } $Output } function Unregister-MountedRegistry { [CmdletBinding()] param( ) if ($null -ne $Script:DefaultRegistryMounted) { Write-Verbose -Message "Unregister-MountedRegistry - Dismounting HKEY_USERS\.DEFAULT_USER" $null = Dismount-PSRegistryPath -MountPoint "HKEY_USERS\.DEFAULT_USER" $Script:DefaultRegistryMounted = $null } if ($null -ne $Script:OfflineRegistryMounted) { foreach ($Key in $Script:OfflineRegistryMounted.Keys) { if ($Script:OfflineRegistryMounted[$Key].Status -eq $true) { Write-Verbose -Message "Unregister-MountedRegistry - Dismounting HKEY_USERS\$Key" $null = Dismount-PSRegistryPath -MountPoint "HKEY_USERS\$Key" } } $Script:OfflineRegistryMounted = $null } } function ConvertTo-HkeyUser { [CmdletBinding()] param( [System.Collections.IDictionary] $HiveDictionary, [Array] $SubKeys, [string] $DictionaryKey, [string] $RegistryPath ) $OutputRegistryKeys = foreach ($Sub in $Subkeys) { if ($HiveDictionary[$DictionaryKey] -eq 'All') { if ($Sub -notlike "*_Classes*" -and $Sub -ne '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'All+Default') { if ($Sub -notlike "*_Classes*") { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } if ($Sub -eq '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } else { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } } elseif ($HiveDictionary[$DictionaryKey] -eq 'Default') { if ($Sub -eq '.DEFAULT') { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Default') { if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") -or $Sub -eq '.DEFAULT') { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } if ($Sub -eq '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } else { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Other') { if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*")) { if (-not $Script:OfflineRegistryMounted) { $Script:OfflineRegistryMounted = Mount-AllRegistryPath foreach ($Key in $Script:OfflineRegistryMounted.Keys) { $RegistryPath.Replace($DictionaryKey, "Users\$Key") } } $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Other+Default') { if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") -or $Sub -eq '.DEFAULT') { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } if (-not $Script:OfflineRegistryMounted) { $Script:OfflineRegistryMounted = Mount-AllRegistryPath foreach ($Key in $Script:OfflineRegistryMounted.Keys) { $RegistryPath.Replace($DictionaryKey, "Users\$Key") } } if ($Sub -eq '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } else { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain') { if ($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'Users') { if ($Sub -like "Offline_*") { $Script:OfflineRegistryMounted = Mount-AllRegistryPath -MountUsers $Sub foreach ($Key in $Script:OfflineRegistryMounted.Keys) { if ($Script:OfflineRegistryMounted[$Key].Status -eq $true) { $RegistryPath } } } } } $OutputRegistryKeys | Sort-Object -Unique } function Dismount-PSRegistryPath { [alias('Dismount-RegistryPath')] [cmdletbinding()] param( [Parameter(Mandatory)][string] $MountPoint, [switch] $Suppress ) [gc]::Collect() $pinfo = [System.Diagnostics.ProcessStartInfo]::new() $pinfo.FileName = "reg.exe" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.Arguments = " unload $MountPoint" $pinfo.CreateNoWindow = $true $pinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden $p = [System.Diagnostics.Process]::new() $p.StartInfo = $pinfo $p.Start() | Out-Null $p.WaitForExit() $Output = $p.StandardOutput.ReadToEnd() $Errors = $p.StandardError.ReadToEnd() if ($Errors) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw $Errors } else { Write-Warning -Message "Dismount-PSRegistryPath - Couldn't unmount $MountPoint. $Errors" } } else { if ($Output -like "*operation completed*") { if (-not $Suppress) { return $true } } } if (-not $Suppress) { return $false } } function Mount-AllRegistryPath { [CmdletBinding()] param( [string] $MountPoint = "HKEY_USERS\", [string] $MountUsers ) $AllProfiles = Get-OfflineRegistryProfilesPath foreach ($Profile in $AllProfiles.Keys) { if ($MountUsers) { if ($MountUsers -ne $Profile) { continue } } $WhereMount = "$MountPoint\$Profile".Replace("\\", "\") Write-Verbose -Message "Mount-OfflineRegistryPath - Mounting $WhereMount to $($AllProfiles[$Profile].FilePath)" $AllProfiles[$Profile].Status = Mount-PSRegistryPath -MountPoint $WhereMount -FilePath $AllProfiles[$Profile].FilePath } $AllProfiles } function Mount-DefaultRegistryPath { [CmdletBinding()] param( [string] $MountPoint = "HKEY_USERS\.DEFAULT_USER" ) $DefaultRegistryPath = Get-PSRegistry -RegistryPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -Key 'Default' -ExpandEnvironmentNames -DoNotUnmount if ($PSError -ne $true) { $PathToNTUser = [io.path]::Combine($DefaultRegistryPath.PSValue, 'NTUSER.DAT') Write-Verbose -Message "Mount-DefaultRegistryPath - Mounting $MountPoint to $PathToNTUser" Mount-PSRegistryPath -MountPoint $MountPoint -FilePath $PathToNTUser } else { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw $PSErrorMessage } else { Write-Warning -Message "Mount-DefaultRegistryPath - Couldn't execute. Error: $PSErrorMessage" } } } function Get-OfflineRegistryProfilesPath { <# .SYNOPSIS Short description .DESCRIPTION Long description .EXAMPLE Get-OfflineRegistryProfilesPath .NOTES Name Value ---- ----- Przemek {[FilePath, C:\Users\Przemek\NTUSER.DAT], [Status, ]} test.1 {[FilePath, C:\Users\test.1\NTUSER.DAT], [Status, ]} #> [CmdletBinding()] param( ) $Profiles = [ordered] @{} $CurrentMapping = (Get-PSRegistry -RegistryPath 'HKEY_USERS' -ExpandEnvironmentNames -DoNotUnmount).PSSubKeys $UsersInSystem = (Get-PSRegistry -RegistryPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -ExpandEnvironmentNames -DoNotUnmount).PSSubKeys $MissingProfiles = foreach ($Profile in $UsersInSystem) { if ($Profile.StartsWith("S-1-5-21") -and $CurrentMapping -notcontains $Profile) { Get-PSRegistry -RegistryPath "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$Profile" -ExpandEnvironmentNames -DoNotUnmount } } foreach ($Profile in $MissingProfiles) { $PathToNTUser = [io.path]::Combine($Profile.ProfileImagePath, 'NTUSER.DAT') $ProfileName = [io.path]::GetFileName($Profile.ProfileImagePath) $StartPath = "Offline_$ProfileName" try { $PathExists = Test-Path -LiteralPath $PathToNTUser -ErrorAction Stop if ($PathExists) { $Profiles[$StartPath] = [ordered] @{ FilePath = $PathToNTUser Status = $null } } } catch { Write-Warning -Message "Mount-OfflineRegistryPath - Couldn't execute. Error: $($_.Exception.Message)" continue } } $Profiles } function Mount-PSRegistryPath { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER MountPoint Parameter description .PARAMETER FilePath Parameter description .EXAMPLE Mount-PSRegistryPath -MountPoint 'HKEY_USERS\.DEFAULT_USER111' -FilePath 'C:\Users\Default\NTUSER.DAT' .NOTES General notes #> [alias('Mount-RegistryPath')] [cmdletbinding()] param( [Parameter(Mandatory)][string] $MountPoint, [Parameter(Mandatory)][string] $FilePath ) $pinfo = [System.Diagnostics.ProcessStartInfo]::new() $pinfo.FileName = "reg.exe" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.Arguments = " load $MountPoint $FilePath" $pinfo.CreateNoWindow = $true $pinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden $p = [System.Diagnostics.Process]::new() $p.StartInfo = $pinfo $p.Start() | Out-Null $p.WaitForExit() $Output = $p.StandardOutput.ReadToEnd() $Errors = $p.StandardError.ReadToEnd() if ($Errors) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw $Errors } else { Write-Warning -Message "Mount-PSRegistryPath - Couldn't mount $MountPoint. $Errors" } } else { if ($Output -like "*operation completed*") { if (-not $Suppress) { return $true } } } if (-not $Suppress) { return $false } } function Add-ACLRule { [CmdletBinding()] param( $AccessRuleToAdd, $ntSecurityDescriptor, $ACL ) try { Write-Verbose "Add-ADACL - Adding access for $($AccessRuleToAdd.IdentityReference) / $($AccessRuleToAdd.ActiveDirectoryRights) / $($AccessRuleToAdd.AccessControlType) / $($AccessRuleToAdd.ObjectType) / $($AccessRuleToAdd.InheritanceType) to $($ACL.DistinguishedName)" if ($ACL.ACL) { $ntSecurityDescriptor = $ACL.ACL } elseif ($ntSecurityDescriptor) { } else { Write-Warning "Add-PrivateACL - No ACL or ntSecurityDescriptor specified" return } $ntSecurityDescriptor.AddAccessRule($AccessRuleToAdd) @{ Success = $true; Reason = $null } } catch { if ($_.Exception.Message -like "*Some or all identity references could not be translated.*") { Write-Warning "Add-ADACL - Error adding permissions for $($AccessRuleToAdd.IdentityReference) / $($AccessRuleToAdd.ActiveDirectoryRights) due to error: $($_.Exception.Message). Retrying with SID" # $SplittedName = $Principal -split '/' # [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.SecurityIdentifier]::new($SplittedName[1]) # $ResolvedIdenity = Convert-Identity -Identity $Principal #$AccessRuleToAdd = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType) #$ntSecurityDescriptor.AddAccessRule($AccessRuleToAdd) @{ Success = $false; Reason = "Identity" } } else { Write-Warning "Add-ADACL - Error adding permissions for $($AccessRuleToAdd.IdentityReference) / $($AccessRuleToAdd.ActiveDirectoryRights) due to error: $($_.Exception.Message)" @{ Success = $false; Reason = $($_.Exception.Message) } } } } function Add-PrivateACL { [cmdletBinding(SupportsShouldProcess)] param( [PSCustomObject] $ACL, [string] $ADObject, [string] $Principal, [alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights] $AccessRule, [System.Security.AccessControl.AccessControlType] $AccessControlType, [alias('ObjectTypeName')][string] $ObjectType, [alias('InheritedObjectTypeName')][string] $InheritedObjectType, [alias('ActiveDirectorySecurityInheritance')][nullable[System.DirectoryServices.ActiveDirectorySecurityInheritance]] $InheritanceType, [alias('ActiveDirectorySecurity')][System.DirectoryServices.ActiveDirectorySecurity] $NTSecurityDescriptor, [System.DirectoryServices.ActiveDirectoryAccessRule] $ActiveDirectoryAccessRule ) if ($ACL) { $ADObject = $ACL.DistinguishedName } else { if (-not $ADObject) { Write-Warning "Add-PrivateACL - No ACL or ADObject specified" return } } $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $ADObject if (-not $DomainName) { Write-Warning -Message "Add-PrivateACL - Unable to determine domain name for $($ADObject)" return } $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] if (-not $ActiveDirectoryAccessRule) { 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) } } $OutputRequiresCommit = @( $newActiveDirectoryAccessRuleSplat = @{ Identity = $Identity ActiveDirectoryAccessRule = $ActiveDirectoryAccessRule ObjectType = $ObjectType InheritanceType = $InheritanceType InheritedObjectType = $InheritedObjectType AccessControlType = $AccessControlType AccessRule = $AccessRule } Remove-EmptyValue -Hashtable $newActiveDirectoryAccessRuleSplat $AccessRuleToAdd = New-ActiveDirectoryAccessRule @newActiveDirectoryAccessRuleSplat if ($AccessRuleToAdd) { $RuleAdded = Add-ACLRule -AccessRuleToAdd $AccessRuleToAdd -ntSecurityDescriptor $NTSecurityDescriptor -ACL $ACL if (-not $RuleAdded.Success -and $RuleAdded.Reason -eq 'Identity') { # rule failed to add, so we need to convert the identity and try with SID $AlternativeSID = (Convert-Identity -Identity $Identity).SID [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.SecurityIdentifier]::new($AlternativeSID) $newActiveDirectoryAccessRuleSplat = @{ Identity = $Identity ActiveDirectoryAccessRule = $ActiveDirectoryAccessRule ObjectType = $ObjectType InheritanceType = $InheritanceType InheritedObjectType = $InheritedObjectType AccessControlType = $AccessControlType AccessRule = $AccessRule } Remove-EmptyValue -Hashtable $newActiveDirectoryAccessRuleSplat $AccessRuleToAdd = New-ActiveDirectoryAccessRule @newActiveDirectoryAccessRuleSplat $RuleAdded = Add-ACLRule -AccessRuleToAdd $AccessRuleToAdd -ntSecurityDescriptor $NTSecurityDescriptor -ACL $ACL } # lets now return value $RuleAdded.Success } else { Write-Warning -Message "Add-PrivateACL - Unable to create ActiveDirectoryAccessRule for $($ADObject). Skipped." $false } ) if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) { Write-Verbose "Add-ADACL - Saving permissions for $($ADObject)" Set-ADObject -Identity $ADObject -Replace @{ ntSecurityDescriptor = $ntSecurityDescriptor } -ErrorAction Stop -Server $QueryServer } elseif ($OutputRequiresCommit -contains $false) { Write-Warning "Add-ADACL - Skipping saving permissions for $($ADObject) due to errors." } } $Script:ConfigurationACLOwners = [ordered] @{ Name = 'Forest ACL Owners' Enabled = $true Execute = { Get-WinADACLForest -Owner #-ExcludeOwnerType Administrative, WellKnownAdministrative } Processing = { $Script:Reporting['ForestACLOwners']['Variables']['OwnersAdministrative'] = 0 $Script:Reporting['ForestACLOwners']['Variables']['OwnersWellKnownAdministrative'] = 0 $Script:Reporting['ForestACLOwners']['Variables']['OwnersUnknown'] = 0 $Script:Reporting['ForestACLOwners']['Variables']['OwnersNotAdministrative'] = 0 $Script:Reporting['ForestACLOwners']['Variables']['RequiringFix'] = 0 $Script:Reporting['ForestACLOwners']['Variables']['Total'] = 0 $Script:Reporting['ForestACLOwners']['LimitedData'] = foreach ($Object in $Script:Reporting['ForestACLOwners']['Data']) { if ($Object.OwnerType -eq 'Administrative') { $Script:Reporting['ForestACLOwners']['Variables']['OwnersAdministrative']++ } elseif ($Object.OwnerType -eq 'WellKnownAdministrative') { $Script:Reporting['ForestACLOwners']['Variables']['OwnersWellKnownAdministrative']++ } elseif ($Object.OwnerType -eq 'NotAdministrative') { $Script:Reporting['ForestACLOwners']['Variables']['OwnersNotAdministrative']++ $Script:Reporting['ForestACLOwners']['Variables']['RequiringFix']++ $Object } else { $Script:Reporting['ForestACLOwners']['Variables']['OwnersUnknown']++ $Script:Reporting['ForestACLOwners']['Variables']['RequiringFix']++ $Object } $Script:Reporting['ForestACLOwners']['Variables']['Total']++ } } Summary = { New-HTMLText -TextBlock { "This report focuses on finding non-administrative owners owning an object in Active Directory. " "It goes thru every single computer, user, group, organizational unit (and other) object and find if the owner is " "Administrative (Domain Admins/Enterprise Admins)" " or " "WellKnownAdministrative (SYSTEM account or similar)" ". If it's not any of that it exposes those objects to be fixed." } -FontSize 10pt -LineBreak New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Forest ACL Owners in Total: ', $Script:Reporting['ForestACLOwners']['Variables']['Total'] -FontWeight normal, bold New-HTMLListItem -Text 'Forest ACL Owners ', 'Domain Admins / Enterprise Admins' , ' as Owner: ', $Script:Reporting['ForestACLOwners']['Variables']['OwnersAdministrative'] -FontWeight normal, bold, normal, bold New-HTMLListItem -Text 'Forest ACL Owners ', 'BUILTIN\Administrators / SYSTEM', ' as Owner: ', $Script:Reporting['ForestACLOwners']['Variables']['OwnersWellKnownAdministrative'] -FontWeight normal, bold, normal, bold New-HTMLListItem -Text "Forest ACL Owners requiring change: ", $Script:Reporting['ForestACLOwners']['Variables']['RequiringFix'] -FontWeight normal, bold { New-HTMLList -Type Unordered { New-HTMLListItem -Text 'Not Administrative: ', $Script:Reporting['ForestACLOwners']['Variables']['OwnersNotAdministrative'] -FontWeight normal, bold New-HTMLListItem -Text 'Unknown (deleted objects/old trusts): ', $Script:Reporting['ForestACLOwners']['Variables']['OwnersUnknown'] -FontWeight normal, bold } } } -FontSize 10pt } Variables = @{ } Solution = { New-HTMLSection -Invisible { New-HTMLPanel { & $Script:ConfigurationACLOwners['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartPie -Name 'Administrative Owners' -Value $Script:Reporting['ForestACLOwners']['Variables']['OwnersAdministrative'] -Color SpringGreen New-ChartPie -Name 'WellKnown Administrative Owners' -Value $Script:Reporting['ForestACLOwners']['Variables']['OwnersWellKnownAdministrative'] -Color SpringGreen New-ChartPie -Name 'Unknown Owners' -Value $Script:Reporting['ForestACLOwners']['Variables']['OwnersUnknown'] -Color BrilliantRose New-ChartPie -Name 'Not Administrative Owners' -Value $Script:Reporting['ForestACLOwners']['Variables']['OwnersNotAdministrative'] -Color Salmon } -Title 'Forest ACL Owners' -TitleAlignment center } } New-HTMLSection -Name 'Forest ACL Owners' { #if ($Script:Reporting['ForestACLOwners']['Data']) { New-HTMLTable -DataTable $Script:Reporting['ForestACLOwners']['LimitedData'] -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 #New-HTMLTableCondition -Name 'PasswordNotRequired' -ComparisonType string -Operator eq -Value $false -BackgroundColor LimeGreen -FailBackgroundColor Alizarin #New-HTMLTableCondition -Name 'PasswordExpired' -ComparisonType string -Operator eq -Value $false -BackgroundColor LimeGreen -FailBackgroundColor Alizarin } #} } if ($Script:Reporting['Settings']['HideSteps'] -eq $false) { New-HTMLSection -Name 'Steps to fix ownership of non-compliant objects in whole forest/domain' { New-HTMLContainer { New-HTMLSpanStyle -FontSize 10pt { New-HTMLWizard { New-HTMLWizardStep -Name 'Prepare environment' { New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery." New-HTMLCodeBlock -Code { Install-Module ADEssentials -Force Import-Module ADEssentials -Force } -Style powershell New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step." } New-HTMLWizardStep -Name 'Prepare a report (up to date)' { New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with removal. To generate new report please use:" New-HTMLCodeBlock -Code { Invoke-ADEssentials -FilePath $Env:UserProfile\Desktop\ADEssentials-ForestACLOwners.html -Verbose -Type ForestACLOwners } New-HTMLText -TextBlock { "When executed it will take a while to generate all data and provide you with new report depending on size of environment." "Once confirmed that data is still showing issues and requires fixing please proceed with next step." } New-HTMLText -Text "Alternatively if you prefer working with console you can run: " New-HTMLCodeBlock -Code { $ForestACLOwner = Get-WinADACLForest -Owner -Verbose -ExcludeOwnerType Administrative, WellKnownAdministrative $ForestACLOwner | Format-Table } New-HTMLText -Text "It includes all the data as you see in table above including all the owner types (including administrative and wellknownadministrative)" } New-HTMLWizardStep -Name 'Fix Owners' { New-HTMLText -Text @( "Following command when executed, finds all object owners within Forest/Domain that doesn't match WellKnownAdministrative (SYSTEM/BUIILTIN\Administrator) or Administrative (Domain Admins/Enterprise Admins) ownership. " "Once it finds those non-compliant owners it replaces them with Domain Admins for a given domain. It doesn't change/modify compliant owners." ) New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black New-HTMLCodeBlock -Code { Set-WinADForestACLOwner -WhatIf -Verbose -IncludeOwnerType 'NotAdministrative', 'Unknown' } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Set-WinADForestACLOwner -WhatIf -Verbose -IncludeOwnerType 'NotAdministrative', 'Unknown' -IncludeDomains 'YourDomainYouHavePermissionsFor' } New-HTMLText -TextBlock { "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be changed matches expected data. " } -LineBreak New-HTMLText -Text "Once happy with results please follow with command (this will start replacement of owners process): " -LineBreak -FontWeight bold New-HTMLText -TextBlock { "This command when executed sets new owner only on first X non-compliant AD objects (computers/users/organizational units/contacts etc.). " "Use LimitProcessing parameter to prevent mass change and increase the counter when no errors occur. " "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. " } New-HTMLCodeBlock -Code { Set-WinADForestACLOwner -Verbose -LimitProcessing 2 -IncludeOwnerType 'NotAdministrative', 'Unknown' } New-HTMLText -TextBlock { "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: " } New-HTMLCodeBlock -Code { Set-WinADForestACLOwner -Verbose -LimitProcessing 2 -IncludeOwnerType 'NotAdministrative', 'Unknown'-IncludeDomains 'YourDomainYouHavePermissionsFor' } } } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors } } } } if ($Script:Reporting['ForestACLOwners']['WarningsAndErrors']) { New-HTMLSection -Name 'Warnings & Errors to Review' { New-HTMLTable -DataTable $Script:Reporting['ForestACLOwners']['WarningsAndErrors'] -Filtering { New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row } } } } } $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 } # Define event #New-ChartEvent -DataTableID 'NewIDtoSearchInChart' -ColumnID 0 } 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 'Encrypted' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor Salmon #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 Salmon -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 Salmon -HighlightHeaders PasswordLastSet, PasswordLastChangedDays #New-HTMLTableCondition -Name 'PasswordNotRequired' -ComparisonType string -Operator eq -Value $false -BackgroundColor LimeGreen -FailBackgroundColor Alizarin #New-HTMLTableCondition -Name 'PasswordExpired' -ComparisonType string -Operator eq -Value $false -BackgroundColor LimeGreen -FailBackgroundColor Alizarin } } } } # https://www.flaticon.com/free-icons/group - group icons - Group icons created by Freepik # https://www.flaticon.com/free-icons/people - people icons - People icons created by Freepik # https://www.flaticon.com/free-icons/person - person icons - Person icons created by photo3idea_studio # https://www.flaticon.com/free-icons/monitor - monitor icons - Monitor icons created by Nikita Golubev $Script:ConfigurationIcons = @{ ImageGroup = 'https://cdn-icons-png.flaticon.com/512/3791/3791146.png' ImageGroupNested = 'https://cdn-icons-png.flaticon.com/512/476/476863.png' ImageGroupCircular = 'https://cdn-icons-png.flaticon.com/512/745/745205.png' ImageComputer = 'https://cdn-icons-png.flaticon.com/512/2289/2289389.png' ImageUser = 'https://cdn-icons-png.flaticon.com/512/3048/3048122.png' ImageOther = 'https://cdn-icons-png.flaticon.com/512/8090/8090771.png' } $Script:ConfigurationLAPS = [ordered] @{ Name = 'LAPS Summary' Enabled = $true Execute = { Get-WinADBitlockerLapsSummary -LapsOnly } Processing = { foreach ($Computer in $Script:Reporting['LAPS']['Data']) { $Script:Reporting['LAPS']['Variables']['ComputersTotal']++ if ($Computer.Enabled) { $Script:Reporting['LAPS']['Variables']['ComputersEnabled']++ if ($Computer.LastLogonDays -lt 60 -and $Computer.System -like "Windows*" -and $Computer.Enabled -eq $true) { if (($Computer.Laps -eq $true -or $Computer.WindowsLaps -eq $true)) { $Script:Reporting['LAPS']['Variables']['ComputersActiveWithLaps']++ } else { # we exclude DC from this count, even tho Windows LAPS is supported there if ($Computer.IsDC -eq $false) { $Script:Reporting['LAPS']['Variables']['ComputersActiveNoLaps']++ } } } if ($Computer.LastLogonDays -gt 360) { $Script:Reporting['LAPS']['Variables']['ComputersOver360days']++ } elseif ($Computer.LastLogonDays -gt 180) { $Script:Reporting['LAPS']['Variables']['ComputersOver180days']++ } elseif ($Computer.LastLogonDays -gt 90) { $Script:Reporting['LAPS']['Variables']['ComputersOver90days']++ } elseif ($Computer.LastLogonDays -gt 60) { $Script:Reporting['LAPS']['Variables']['ComputersOver60days']++ } elseif ($Computer.LastLogonDays -gt 30) { $Script:Reporting['LAPS']['Variables']['ComputersOver30days']++ } elseif ($Computer.LastLogonDays -gt 15) { $Script:Reporting['LAPS']['Variables']['ComputersOver15days']++ } else { $Script:Reporting['LAPS']['Variables']['ComputersRecent']++ } } else { $Script:Reporting['LAPS']['Variables']['ComputersDisabled']++ } if (($Computer.Laps -eq $true -or $Computer.WindowsLaps -eq $true) -and $Computer.Enabled -eq $true) { $Script:Reporting['LAPS']['Variables']['ComputersLapsEnabled']++ if ($Computer.LapsExpirationDays -lt 0 -or $Computer.WindowsLapsExpirationDays -lt 0) { $Script:Reporting['LAPS']['Variables']['ComputersLapsExpired']++ } else { $Script:Reporting['LAPS']['Variables']['ComputersLapsNotExpired']++ } } elseif ($Computer.Enabled -eq $true) { if ($Computer.System -notlike "Windows*") { # since Windows LAPS is supported on DC as well we only check for Windows $Script:Reporting['LAPS']['Variables']['ComputersLapsNotApplicable']++ } else { $Script:Reporting['LAPS']['Variables']['ComputersLapsDisabled']++ } } if ($Computer.LastLogonDays -gt 60) { $Script:Reporting['LAPS']['Variables']['ComputersInactive']++ } else { $Script:Reporting['LAPS']['Variables']['ComputersActive']++ } if ($Computer.System -like "Windows Server*") { $Script:Reporting['LAPS']['Variables']['ComputersServer']++ if ($Computer.Enabled) { $Script:Reporting['LAPS']['Variables']['ComputersServerEnabled']++ if ($Computer.Laps -eq $true -or $Computer.WindowsLaps -eq $true) { $Script:Reporting['LAPS']['Variables']['ComputersServerLapsEnabled']++ } else { $Script:Reporting['LAPS']['Variables']['ComputersServerLapsDisabled']++ } } else { $Script:Reporting['LAPS']['Variables']['ComputersServerDisabled']++ } } elseif ($Computer.System -notlike "Windows Server*" -and $Computer.System -like "Windows*") { $Script:Reporting['LAPS']['Variables']['ComputersWorkstation']++ if ($Computer.Enabled) { $Script:Reporting['LAPS']['Variables']['ComputersWorkstationEnabled']++ if ($Computer.Laps -eq $true -or $Computer.WindowsLaps -eq $true) { $Script:Reporting['LAPS']['Variables']['ComputersWorkstationLapsEnabled']++ } else { $Script:Reporting['LAPS']['Variables']['ComputersWorkstationLapsDisabled']++ } } else { $Script:Reporting['LAPS']['Variables']['ComputersWorkstationDisabled']++ } } else { $Script:Reporting['LAPS']['Variables']['ComputersOther']++ if ($Computer.Enabled) { $Script:Reporting['LAPS']['Variables']['ComputersOtherEnabled']++ if ($Computer.Laps -eq $true -or $Computer.WindowsLaps -eq $true) { $Script:Reporting['LAPS']['Variables']['ComputersOtherLapsEnabled']++ } else { $Script:Reporting['LAPS']['Variables']['ComputersOtherLapsDisabled']++ } } else { $Script:Reporting['LAPS']['Variables']['ComputersOtherDisabled']++ } } } } Summary = { New-HTMLText -Text @( "This report focuses on showing LAPS status of all computer objects in the domain. " "It shows how many computers are enabled, disabled, have LAPS enabled, disabled, expired, etc." "It's perfectly normal that some LAPS passwords are expired, due to working over VPN etc." ) -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 "Here's an overview of some statistics about computers:" -FontSize 10pt New-HTMLList { New-HTMLListItem -Text "Total number of computers: ", $($Script:Reporting['LAPS']['Variables'].ComputersTotal) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of enabled computers: ", $($Script:Reporting['LAPS']['Variables'].ComputersEnabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of disabled computers: ", $($Script:Reporting['LAPS']['Variables'].ComputersDisabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of active computers (less then 60 days): ", $($Script:Reporting['LAPS']['Variables'].ComputersActive) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of inactive computers (over 60 days): ", $($Script:Reporting['LAPS']['Variables'].ComputersInactive) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of active computers with LAPS (less then 60 days): ", $($Script:Reporting['LAPS']['Variables'].ComputersActiveWithLaps) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of active computers without LAPS (less then 60 days): ", $($Script:Reporting['LAPS']['Variables'].ComputersActiveNoLaps) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of computers (enabled) with LAPS: ", $($Script:Reporting['LAPS']['Variables'].ComputersLapsEnabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of computers (enabled) without LAPS: ", $($Script:Reporting['LAPS']['Variables'].ComputersLapsDisabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of servers (enabled): ", $($Script:Reporting['LAPS']['Variables'].ComputersServerEnabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of servers (enabled) with LAPS: ", $($Script:Reporting['LAPS']['Variables'].ComputersServerLapsEnabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of servers (enabled) without LAPS: ", $($Script:Reporting['LAPS']['Variables'].ComputersServerLapsDisabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of servers (disabled): ", $($Script:Reporting['LAPS']['Variables'].ComputersServerDisabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of workstations (enabled) with LAPS: ", $($Script:Reporting['LAPS']['Variables'].ComputersWorkstationLapsEnabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of workstations (enabled) without LAPS: ", $($Script:Reporting['LAPS']['Variables'].ComputersWorkstationLapsDisabled) -Color None, BlueMarguerite -FontWeight normal, bold } -FontSize 10pt } Variables = @{ ComputersActiveNoLaps = 0 ComputersActiveWithLaps = 0 ComputersTotal = 0 ComputersEnabled = 0 ComputersDisabled = 0 ComputersActive = 0 ComputersInactive = 0 ComputersLapsEnabled = 0 ComputersLapsDisabled = 0 ComputersLapsNotApplicable = 0 ComputersLapsExpired = 0 ComputersLapsNotExpired = 0 ComputersServer = 0 ComputersServerEnabled = 0 ComputersServerDisabled = 0 ComputersServerLapsEnabled = 0 ComputersServerLapsDisabled = 0 ComputersServerLapsNotApplicable = 0 ComputersWorkstation = 0 ComputersWorkstationEnabled = 0 ComputersWorkstationDisabled = 0 ComputersWorkstationLapsEnabled = 0 ComputersWorkstationLapsDisabled = 0 ComputersOther = 0 ComputersOtherEnabled = 0 ComputersOtherDisabled = 0 ComputersOtherLapsEnabled = 0 ComputersOtherLapsDisabled = 0 ComputersOver360days = 0 ComputersOver180days = 0 ComputersOver90days = 0 ComputersOver60days = 0 ComputersOver30days = 0 ComputersOver15days = 0 ComputersRecent = 0 } Solution = { if ($Script:Reporting['LAPS']['Data']) { New-HTMLSection -Invisible { New-HTMLPanel { $Script:Reporting['LAPS']['Summary'] } New-HTMLPanel { New-HTMLCarousel -Height auto -Loop { New-CarouselSlide -Height auto { New-HTMLChart { New-ChartBarOptions -Type bar New-ChartLegend -Name 'Active Computers (by last logon age)' -Color SpringGreen, Salmon New-ChartBar -Name 'Computers (over 360 days)' -Value $Script:Reporting['LAPS']['Variables'].ComputersOver360days New-ChartBar -Name 'Computers (over 180 days)' -Value $Script:Reporting['LAPS']['Variables'].ComputersOver180days New-ChartBar -Name 'Computers (over 90 days)' -Value $Script:Reporting['LAPS']['Variables'].ComputersOver90days New-ChartBar -Name 'Computers (over 60 days)' -Value $Script:Reporting['LAPS']['Variables'].ComputersOver60days New-ChartBar -Name 'Computers (over 30 days)' -Value $Script:Reporting['LAPS']['Variables'].ComputersOver30days New-ChartBar -Name 'Computers (over 15 days)' -Value $Script:Reporting['LAPS']['Variables'].ComputersOver15days New-ChartBar -Name 'Computers (Recent)' -Value $Script:Reporting['LAPS']['Variables'].ComputersRecent New-ChartAxisY -LabelMaxWidth 300 -Show } -Title 'Active Computers' -TitleAlignment center } New-CarouselSlide -Height auto { New-HTMLChart -Gradient { New-ChartPie -Name 'Computers Enabled' -Value $Script:Reporting['LAPS']['Variables'].ComputersEnabled New-ChartPie -Name 'Computers Disabled' -Value $Script:Reporting['LAPS']['Variables'].ComputersDisabled } -Title "Enabled vs Disabled All Computer Objects" } New-CarouselSlide -Height auto { New-HTMLChart -Gradient { New-ChartPie -Name 'Clients enabled' -Value $Script:Reporting['LAPS']['Variables'].ComputersWorkstationEnabled New-ChartPie -Name 'Clients disabled' -Value $Script:Reporting['LAPS']['Variables'].ComputersWorkstationDisabled } -Title "Enabled vs Disabled Workstations" } New-CarouselSlide -Height auto { New-HTMLChart -Gradient { New-ChartPie -Name 'Servers enabled' -Value $Script:Reporting['LAPS']['Variables'].ComputersServerEnabled New-ChartPie -Name 'Servers disabled' -Value $Script:Reporting['LAPS']['Variables'].ComputersServerDisabled } -Title "Enabled vs Disabled Servers" } New-CarouselSlide -Height auto { New-HTMLChart -Gradient { New-ChartPie -Name 'Servers' -Value $Script:Reporting['LAPS']['Variables'].ComputersServer New-ChartPie -Name 'Clients' -Value $Script:Reporting['LAPS']['Variables'].ComputersWorkstation New-ChartPie -Name 'Non-Windows' -Value $Script:Reporting['LAPS']['Variables'].ComputersOther } -Title "Computers by Type" } } } } New-HTMLSection -HeaderText 'General statistics' -CanCollapse { New-HTMLPanel { New-HTMLCarousel -Height auto -Loop { New-CarouselSlide -Height auto { New-HTMLChart -Gradient { New-ChartPie -Name 'With LAPS' -Value $Script:Reporting['LAPS']['Variables'].ComputersLapsEnabled -Color '#94ffc8' New-ChartPie -Name 'Without LAPS' -Value $Script:Reporting['LAPS']['Variables'].ComputersLapsDisabled -Color 'Salmon' New-ChartPie -Name 'LAPS N/A' -Value $Script:Reporting['LAPS']['Variables'].ComputersLapsNotApplicable -Color 'LightGray' } -Title "All Computers with LAPS" } New-CarouselSlide -Height auto { New-HTMLChart -Gradient { New-ChartPie -Name 'With LAPS' -Value $Script:Reporting['LAPS']['Variables'].ComputersActiveWithLaps -Color '#94ffc8' New-ChartPie -Name 'Without LAPS' -Value $Script:Reporting['LAPS']['Variables'].ComputersActiveNoLaps -Color 'Salmon' } -Title "Active Computers with LAPS" -SubTitle "Logged on within the last 60 days" } New-CarouselSlide -Height auto { New-HTMLChart -Gradient { New-ChartPie -Name 'With LAPS' -Value $Script:Reporting['LAPS']['Variables'].ComputersActiveWithLaps -Color '#94ffc8' New-ChartPie -Name 'Without LAPS' -Value $Script:Reporting['LAPS']['Variables'].ComputersActiveNoLaps -Color 'Salmon' } -Title "Active Computers with LAPS" -SubTitle "Logged on within the last 60 days" } } } New-HTMLPanel { New-HTMLCarousel -Height auto -Loop -AutoPlay { New-CarouselSlide -Height auto { New-HTMLChart -Gradient { New-ChartPie -Name 'With LAPS' -Value $Script:Reporting['LAPS']['Variables'].ComputersWorkstationLapsEnabled -Color '#94ffc8' New-ChartPie -Name 'Without LAPS' -Value $Script:Reporting['LAPS']['Variables'].ComputersWorkstationLapsDisabled -Color 'Salmon' } -Title "Workstations with LAPS" } New-CarouselSlide -Height auto { New-HTMLChart -Gradient { New-ChartPie -Name 'With LAPS' -Value $Script:Reporting['LAPS']['Variables'].ComputersServerLapsEnabled -Color '#94ffc8' New-ChartPie -Name 'Without LAPS' -Value $Script:Reporting['LAPS']['Variables'].ComputersServerLapsDisabled -Color 'Salmon' } -Title "Servers with LAPS" } } } } } 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 New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $true -BackgroundColor BlizzardBlue -HighlightHeaders IsDC, Laps, LapsExpirationDays, LapsExpirationTime New-HTMLTableCondition -Name 'WindowsLapsExpirationDays' -ComparisonType number -Operator lt -Value 0 -BackgroundColor BurntOrange -HighlightHeaders WindowsLapsExpirationDays, WindowsLapsExpirationTime -FailBackgroundColor LimeGreen New-HTMLTableCondition -Name 'WindowsLaps' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor Alizarin New-HTMLTableCondition -Name 'WindowsLaps' -ComparisonType string -Operator eq -Value $false -BackgroundColor Alizarin -HighlightHeaders WindowsLaps, WindowsLapsExpirationDays, WindowsLapsExpirationTime New-HTMLTableCondition -Name 'WindowsLaps' -ComparisonType string -Operator eq -Value "" -BackgroundColor BlizzardBlue -HighlightHeaders WindowsLaps, WindowsLapsExpirationDays, WindowsLapsExpirationTime } } } $Script:ConfigurationLAPSACL = [ordered] @{ Name = 'LAPS ACL' Enabled = $true Execute = { Get-WinADComputerACLLAPS -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains } Processing = { foreach ($Object in $Script:Reporting['LAPSACL']['Data']) { if ($Object.Enabled) { $Script:Reporting['LAPSACL']['Variables']['ComputersEnabled']++ if ($Object.LapsACL) { $Script:Reporting['LAPSACL']['Variables']['LapsACL']++ if ($Object.OperatingSystem -like "Windows Server*") { $Script:Reporting['LAPSACL']['Variables']['LapsACLOKServer']++ } elseif ($Object.OperatingSystem -notlike "Windows Server*" -and $Object.OperatingSystem -like "Windows*") { $Script:Reporting['LAPSACL']['Variables']['LapsACLOKClient']++ } } else { if ($Object.IsDC -eq $false) { $Script:Reporting['LAPSACL']['Variables']['LapsACLNot']++ if ($Object.OperatingSystem -like "Windows Server*") { $Script:Reporting['LAPSACL']['Variables']['LapsACLNotServer']++ } elseif ($Object.OperatingSystem -notlike "Windows Server*" -and $Object.OperatingSystem -like "Windows*") { $Script:Reporting['LAPSACL']['Variables']['LapsACLNotClient']++ } } } } else { $Script:Reporting['LAPSACL']['Variables']['ComputersDisabled']++ } } } 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 = @{ ComputersEnabled = 0 ComputersDisabled = 0 LapsACL = 0 LapsACLNot = 0 LapsACLOKServer = 0 LapsACLOKClient = 0 LapsACLNotServer = 0 LapsACLNotClient = 0 } Solution = { if ($Script:Reporting['LAPSACL']['Data']) { New-HTMLSection -Invisible { New-HTMLPanel { $Script:Reporting['LAPSACL']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type barStacked New-ChartLegend -Names 'Enabled', 'Disabled' -Color SpringGreen, Salmon New-ChartBar -Name 'Computers' -Value $Script:Reporting['LAPSACL']['Variables'].ComputersEnabled, $Script:Reporting['LAPSACL']['Variables'].ComputersDisabled # New-ChartAxisY -LabelMaxWidth 300 -Show } -Title 'Active Computers' -TitleAlignment center } } New-HTMLSection -HeaderText 'General statistics' -CanCollapse { New-HTMLPanel { New-HTMLChart -Gradient { New-ChartPie -Name 'Computers Enabled' -Value $Script:Reporting['LAPSACL']['Variables'].ComputersEnabled New-ChartPie -Name 'Computers Disabled' -Value $Script:Reporting['LAPSACL']['Variables'].ComputersDisabled } -Title "Enabled vs Disabled All Computer Objects" } New-HTMLPanel { New-HTMLChart -Gradient { New-ChartPie -Name 'LAPS ACL OK' -Value $Script:Reporting['LAPSACL']['Variables'].LapsACL New-ChartPie -Name 'LAPS ACL Not OK' -Value $Script:Reporting['LAPSACL']['Variables'].LapsACLNot } -Title "LAPS ACL OK vs Not OK" } New-HTMLPanel { New-HTMLChart -Gradient { New-ChartPie -Name 'LAPS ACL OK - Server' -Value $Script:Reporting['LAPSACL']['Variables'].LapsACLOKServer -Color SpringGreen New-ChartPie -Name 'LAPS ACL OK - Client' -Value $Script:Reporting['LAPSACL']['Variables'].LapsACLOKClient -Color LimeGreen New-ChartPie -Name 'LAPS ACL Not OK - Server' -Value $Script:Reporting['LAPSACL']['Variables'].LapsACLNotServer -Color Salmon New-ChartPie -Name 'LAPS ACL Not OK - Client' -Value $Script:Reporting['LAPSACL']['Variables'].LapsACLNotClient -Color Red } -Title "LAPS ACL OK vs Not OK by Computer Type" } } 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 'WindowsLAPSACL' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen New-HTMLTableCondition -Name 'WindowsLAPSExpirationACL' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen New-HTMLTableCondition -Name 'WindowsLAPSEncryptedPassword' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen New-HTMLTableCondition -Name 'WindowsLAPSACL' -ComparisonType string -Operator eq -Value $false -BackgroundColor Alizarin New-HTMLTableCondition -Name 'WindowsLAPSExpirationACL' -ComparisonType string -Operator eq -Value $false -BackgroundColor Alizarin New-HTMLTableCondition -Name 'WindowsLAPSEncryptedPassword' -ComparisonType string -Operator eq -Value $false -BackgroundColor Alizarin 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 New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $true -BackgroundColor BlizzardBlue -HighlightHeaders WindowsLAPSACL, WindowsLAPSExpirationACL, WindowsLAPSEncryptedPassword } } 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 'Encrypted' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor Salmon 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 New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $true -BackgroundColor BlizzardBlue -HighlightHeaders IsDC, Laps, LapsExpirationDays, LapsExpirationTime New-HTMLTableCondition -Name 'WindowsLapsExpirationDays' -ComparisonType number -Operator lt -Value 0 -BackgroundColor BurntOrange -HighlightHeaders WindowsLapsExpirationDays, WindowsLapsExpirationTime -FailBackgroundColor LimeGreen New-HTMLTableCondition -Name 'WindowsLaps' -ComparisonType string -Operator eq -Value $true -BackgroundColor LimeGreen -FailBackgroundColor Alizarin New-HTMLTableCondition -Name 'WindowsLaps' -ComparisonType string -Operator eq -Value $false -BackgroundColor Alizarin -HighlightHeaders WindowsLaps, WindowsLapsExpirationDays, WindowsLapsExpirationTime New-HTMLTableCondition -Name 'WindowsLaps' -ComparisonType string -Operator eq -Value "" -BackgroundColor BlizzardBlue -HighlightHeaders WindowsLaps, WindowsLapsExpirationDays, WindowsLapsExpirationTime } } } } $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:ShowWinADAccountDelegation = [ordered] @{ Name = 'All Accounts Delegation' Enabled = $true Execute = { Get-WinADDelegatedAccounts } Processing = { } Summary = { } Variables = @{ } Solution = { New-HTMLTable -DataTable $Script:Reporting['AccountDelegation']['Data'] -Filtering { # # highlight whole row as blue if the computer is disabled New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $false -Row -BackgroundColor LightYellow # # highlight enabled column as red if the computer is disabled New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $true -BackgroundColor PaleGreen New-HTMLTableConditionGroup { New-HTMLTableCondition -Name 'FullDelegation' -ComparisonType string -Operator eq -Value $true New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $true New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $false } -BackgroundColor Salmon -HighlightHeaders 'FullDelegation' -FailBackgroundColor PaleGreen New-HTMLTableCondition -Name 'ConstrainedDelegation' -ComparisonType string -Operator eq -Value $true -BackgroundColor PaleGreen -FailBackgroundColor Yellow New-HTMLTableCondition -Name 'ResourceDelegation' -ComparisonType string -Operator eq -Value $true -BackgroundColor PaleGreen -FailBackgroundColor Yellow # # highlight whole row as green if the computer is enabled and LastLogon, PasswordDays Over 30 # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator le -Value 30 # New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator le -Value 30 # } -BackgroundColor PaleGreen -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 30 # New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType string -Operator eq -Value '' # } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType string -Operator eq -Value '' # New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator gt -Value 30 # } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType string -Operator eq -Value '' # New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType string -Operator eq -Value '' # } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # # highlight whole row as green if the computer is enabled and LastLogon, PasswordDays Over 30 # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 30 # New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator gt -Value 30 # } -BackgroundColor Salmon -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'TrustedForDelegation' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $false # } -BackgroundColor Red -HighlightHeaders Name, SamAccountName, TrustedForDelegation, IsDC # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'PasswordNotRequired' -ComparisonType string -Operator eq -Value $True # } -BackgroundColor Red -HighlightHeaders Name, SamAccountName, Enabled, PasswordNotRequired } -ScrollX } } $Script:ShowWinADComputer = [ordered] @{ Name = 'All Computers' Enabled = $true Execute = { Get-WinADComputers -PerDomain -AddOwner } Processing = { foreach ($Domain in $Script:Reporting['Computers']['Data'].Keys) { $Script:Reporting['Computers']['Variables'][$Domain] = [ordered] @{} foreach ($Computer in $Script:Reporting['Computers']['Data'][$Domain]) { $Script:Reporting['Computers']['Variables']['ComputersTotal']++ if ($Computer.Enabled) { $Script:Reporting['Computers']['Variables'][$Domain]['ComputersEnabled']++ $Script:Reporting['Computers']['Variables']['ComputersEnabled']++ if ($Computer.IsDC) { $Script:Reporting['Computers']['Variables'][$Domain]['ComputersDC']++ } else { $Script:Reporting['Computers']['Variables'][$Domain]['ComputersNotDC']++ } if ($Computer.OperatingSystem -like "Windows Server*") { $Script:Reporting['Computers']['Variables'][$Domain]['ComputersServer']++ } elseif ($Computer.OperatingSystem -notlike "Windows Server*" -and $Computer.OperatingSystem -like "Windows*") { $Script:Reporting['Computers']['Variables'][$Domain]['ComputersClient']++ } } else { $Script:Reporting['Computers']['Variables'][$Domain]['ComputersDisabled']++ $Script:Reporting['Computers']['Variables']['ComputersDisabled']++ } if ($Computer.OperatingSystem) { $Script:Reporting['Computers']['Variables']['Systems'][$computer.OperatingSystem]++ } else { $Script:Reporting['Computers']['Variables']['Systems']['Unknown']++ } if ($Computer.OperatingSystem -like "Windows Server*") { $Script:Reporting['Computers']['Variables']['ComputersServer']++ if ($Computer.Enabled) { $Script:Reporting['Computers']['Variables']['ComputersServerEnabled']++ } else { $Script:Reporting['Computers']['Variables']['ComputersServerDisabled']++ } } elseif ($Computer.OperatingSystem -notlike "Windows Server*" -and $Computer.OperatingSystem -like "Windows*") { $Script:Reporting['Computers']['Variables']['ComputersWorkstation']++ if ($Computer.Enabled) { $Script:Reporting['Computers']['Variables']['ComputersWorkstationEnabled']++ } else { $Script:Reporting['Computers']['Variables']['ComputersWorkstationDisabled']++ } } else { $Script:Reporting['Computers']['Variables']['ComputersOther']++ if ($Computer.Enabled) { $Script:Reporting['Computers']['Variables']['ComputersOtherEnabled']++ } else { $Script:Reporting['Computers']['Variables']['ComputersOtherDisabled']++ } } } } } Summary = { New-HTMLText -Text @( "This report focuses on showing status of all computer objects in the Active Directory forest. " "It shows how many computers are enabled, disabled, expired, etc." ) -FontSize 10pt -LineBreak New-HTMLText -Text "Here's an overview of some statistics about computers:" -FontSize 10pt New-HTMLList { New-HTMLListItem -Text "Total number of computers: ", $($Script:Reporting['Computers']['Variables'].ComputersTotal) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of enabled computers: ", $($Script:Reporting['Computers']['Variables'].ComputersEnabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of disabled computers: ", $($Script:Reporting['Computers']['Variables'].ComputersDisabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of workstations: ", $($Script:Reporting['Computers']['Variables'].ComputersWorkstation) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of enabled workstations: ", $($Script:Reporting['Computers']['Variables'].ComputersWorkstationEnabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of disabled workstations: ", $($Script:Reporting['Computers']['Variables'].ComputersWorkstationDisabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of servers: ", $($Script:Reporting['Computers']['Variables'].ComputersServer) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of enabled servers: ", $($Script:Reporting['Computers']['Variables'].ComputersServerEnabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of disabled servers: ", $($Script:Reporting['Computers']['Variables'].ComputersServerDisabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of other computers: ", $($Script:Reporting['Computers']['Variables'].ComputersOther) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of enabled other computers: ", $($Script:Reporting['Computers']['Variables'].ComputersOtherEnabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of disabled other computers: ", $($Script:Reporting['Computers']['Variables'].ComputersOtherDisabled) -Color None, BlueMarguerite -FontWeight normal, bold } -FontSize 10pt } Variables = @{ ComputersTotal = 0 ComputersEnabled = 0 ComputersDisabled = 0 ComputersWorkstation = 0 ComputersWorkstationEnabled = 0 ComputersWorkstationDisabled = 0 ComputersServer = 0 ComputersServerEnabled = 0 ComputersServerDisabled = 0 ComputersOther = 0 ComputersOtherEnabled = 0 ComputersOtherDisabled = 0 Systems = [ordered] @{ Unknown = 0 } } Solution = { if ($Script:Reporting['Computers']['Data'] -is [System.Collections.IDictionary]) { New-HTMLSection -Invisible { New-HTMLPanel { $Script:Reporting['Computers']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type bar New-ChartLegend -Name 'Computers by Operating System' -Color SpringGreen, Salmon foreach ($System in $Script:Reporting['Computers']['Variables'].Systems.Keys) { New-ChartBar -Name $System -Value $Script:Reporting['Computers']['Variables']['Systems'][$System] } New-ChartAxisY -LabelMaxWidth 300 -Show } -Title 'Computers by Operating System' -TitleAlignment center } } New-HTMLSection -HeaderText 'General statistics' -CanCollapse { New-HTMLPanel { New-HTMLChart -Gradient { New-ChartPie -Name 'Computers Enabled' -Value $Script:Reporting['Computers']['Variables'].ComputersEnabled New-ChartPie -Name 'Computers Disabled' -Value $Script:Reporting['Computers']['Variables'].ComputersDisabled } -Title "Enabled vs Disabled All Computer Objects" } New-HTMLPanel { New-HTMLChart -Gradient { New-ChartPie -Name 'Clients enabled' -Value $Script:Reporting['Computers']['Variables'].ComputersWorkstationEnabled New-ChartPie -Name 'Clients disabled' -Value $Script:Reporting['Computers']['Variables'].ComputersWorkstationDisabled } -Title "Enabled vs Disabled Workstations" } New-HTMLPanel { New-HTMLChart -Gradient { New-ChartPie -Name 'Servers enabled' -Value $Script:Reporting['Computers']['Variables'].ComputersServerEnabled New-ChartPie -Name 'Servers disabled' -Value $Script:Reporting['Computers']['Variables'].ComputersServerDisabled } -Title "Enabled vs Disabled Servers" } New-HTMLPanel { New-HTMLChart -Gradient { New-ChartPie -Name 'Servers' -Value $Script:Reporting['Computers']['Variables'].ComputersServer New-ChartPie -Name 'Clients' -Value $Script:Reporting['Computers']['Variables'].ComputersWorkstation New-ChartPie -Name 'Non-Windows' -Value $Script:Reporting['Computers']['Variables'].ComputersOther } -Title "Computers by Type" } } New-HTMLTabPanel { foreach ($Domain in $Script:Reporting['Computers']['Data'].Keys) { New-HTMLTab -Name $Domain { New-HTMLTable -DataTable $Script:Reporting['Computers']['Data'][$Domain] -Filtering { # highlight whole row as blue if the computer is disabled New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $false -Row -BackgroundColor LightYellow # highlight enabled column as red if the computer is disabled New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $false -BackgroundColor Salmon # highlight whole row as green if the computer is enabled and LastLogon, PasswordDays Over 30 New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator le -Value 30 New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator le -Value 30 } -BackgroundColor PaleGreen -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 30 New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType string -Operator eq -Value '' } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType string -Operator eq -Value '' New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator gt -Value 30 } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType string -Operator eq -Value '' New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType string -Operator eq -Value '' } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # highlight whole row as green if the computer is enabled and LastLogon, PasswordDays Over 30 New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 30 New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator gt -Value 30 } -BackgroundColor Salmon -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'TrustedForDelegation' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $false } -BackgroundColor Red -HighlightHeaders Name, SamAccountName, TrustedForDelegation, IsDC New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'PasswordNotRequired' -ComparisonType string -Operator eq -Value $True } -BackgroundColor Red -HighlightHeaders Name, SamAccountName, Enabled, PasswordNotRequired } -ScrollX } } } } } } $Script:ShowWinADGroup = [ordered] @{ Name = 'All Groups' Enabled = $true Execute = { Get-WinADGroups -PerDomain -AddOwner } Processing = { } Summary = { } Variables = @{ } Solution = { if ($Script:Reporting['Groups']['Data'] -is [System.Collections.IDictionary]) { New-HTMLTabPanel { foreach ($Domain in $Script:Reporting['Groups']['Data'].Keys) { New-HTMLTab -Name $Domain { New-HTMLTable -DataTable $Script:Reporting['Groups']['Data'][$Domain] -Filtering { New-HTMLTableColumnOption -ColumnIndex 17 -Width 3000 New-HTMLTableCondition -Name 'ManagerCanUpdateGroupMembership' -ComparisonType string -Operator eq -Value $true -BackgroundColor LightYellow New-HTMLTableCondition -Name 'ManagerCanUpdateGroupMembership' -ComparisonType string -Operator eq -Value $false -BackgroundColor PaleGreen #New-HTMLTableCondition -Name 'GroupWriteBack' -ComparisonType string -Operator eq -Value $true -BackgroundColor Pink #New-HTMLTableCondition -Name 'GroupWriteBack' -ComparisonType string -Operator eq -Value $false -BackgroundColor PaleGreen # highlight whole row as blue if the computer is disabled # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $false -Row -BackgroundColor LightYellow # # highlight enabled column as red if the computer is disabled # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $false -BackgroundColor Salmon # # highlight whole row as green if the computer is enabled and LastLogon, PasswordDays Over 30 # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator le -Value 30 # New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator le -Value 30 # } -BackgroundColor PaleGreen -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 30 # New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType string -Operator eq -Value '' # } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType string -Operator eq -Value '' # New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator gt -Value 30 # } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType string -Operator eq -Value '' # New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType string -Operator eq -Value '' # } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # # highlight whole row as green if the computer is enabled and LastLogon, PasswordDays Over 30 # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 30 # New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator gt -Value 30 # } -BackgroundColor Salmon -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'TrustedForDelegation' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Operator eq -Value $false # } -BackgroundColor Red -HighlightHeaders Name, SamAccountName, TrustedForDelegation, IsDC # New-HTMLTableConditionGroup -Conditions { # New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True # New-HTMLTableCondition -Name 'PasswordNotRequired' -ComparisonType string -Operator eq -Value $True # } -BackgroundColor Red -HighlightHeaders Name, SamAccountName, Enabled, PasswordNotRequired } -ScrollX } } } } } } $Script:ConfigurationPasswordPolicies = [ordered] @{ Name = 'Password Policies Summary' Enabled = $true Execute = { Get-WinADPasswordPolicy } Processing = { foreach ($PasswordPolicy in $Script:Reporting['PasswordPolicies']['Data']) { if ($PasswordPolicy.Name -eq 'Default Password Policy') { $Script:Reporting['PasswordPolicies']['Variables'].DefaultPasswordPolicy += 1 } else { $Script:Reporting['PasswordPolicies']['Variables'].FineGrainedPasswordPolicies += 1 } } } Summary = { New-HTMLText -Text @( "This report focuses on showing all Password Policies in Active Directory forest. " "It shows default password policies and fine grained password policies. " "Keep in mind that there can only be one valid Default Password Policy per domain. " "If you have multiple password policies defined (that are not FGPP), only one will work, the one with the lowest precedence on the Domain Controller OU." "Any other Password Policy that you defined will not be shown here." "If you are not seeing FGPP password policies and you have them defined, make sure that you have extended rights to read them." ) -FontSize 10pt -LineBreak } Variables = @{ } Solution = { if ($Script:Reporting['PasswordPolicies']['Data']) { New-HTMLSection -Invisible { New-HTMLPanel { $Script:Reporting['PasswordPolicies']['Summary'] } } New-HTMLTable -DataTable $Script:Reporting['PasswordPolicies']['Data'] -Filtering -SearchBuilder { New-HTMLTableCondition -Name 'MinPasswordLength' -ComparisonType number -Operator le -Value 8 -BackgroundColor Salmon New-HTMLTableCondition -Name 'MinPasswordLength' -ComparisonType number -Operator le -Value 4 -BackgroundColor Red New-HTMLTableCondition -Name 'MinPasswordLength' -ComparisonType number -Operator between -Value 8, 16 -BackgroundColor Yellow New-HTMLTableCondition -Name 'MinPasswordLength' -ComparisonType number -Operator between -Value 16, 20 -BackgroundColor LightGreen New-HTMLTableCondition -Name 'MinPasswordLength' -ComparisonType number -Operator ge -Value 20 -BackgroundColor Green New-HTMLTableCondition -Name 'ComplexityEnabled' -ComparisonType string -Operator eq -Value $false -BackgroundColor Salmon -FailBackgroundColor LightGreen New-HTMLTableCondition -Name 'ReversibleEncryptionEnabled' -ComparisonType string -Operator eq -Value $true -BackgroundColor Salmon -FailBackgroundColor LightGreen } -ScrollX } } } $Script:ShowWinADUser = [ordered] @{ Name = 'All Users' Enabled = $true Execute = { Get-WinADUsers -PerDomain -AddOwner } Processing = { foreach ($Domain in $Script:Reporting['Users']['Data'].Keys) { foreach ($User in $Script:Reporting['Users']['Data'][$Domain]) { $Script:Reporting['Users']['Variables']['UsersTotal']++ if ($User.Enabled) { $Script:Reporting['Users']['Variables']['UsersEnabled']++ if ($User.PasswordNeverExpires) { $Script:Reporting['Users']['Variables']['PasswordNeverExpires']++ } else { $Script:Reporting['Users']['Variables']['PasswordExpires']++ } if ($User.PasswordNotRequired) { $Script:Reporting['Users']['Variables']['PasswordNotRequired']++ } if ($User.PasswordLastDays -gt 360) { $Script:Reporting['Users']['Variables']['PasswordLastDays360']++ } elseif ($User.PasswordLastDays -gt 300) { $Script:Reporting['Users']['Variables']['PasswordLastDays300']++ } elseif ($User.PasswordLastDays -gt 180) { $Script:Reporting['Users']['Variables']['PasswordLastDays180']++ } elseif ($User.PasswordLastDays -gt 90) { $Script:Reporting['Users']['Variables']['PasswordLastDays90']++ } elseif ($User.PasswordLastDays -gt 60) { $Script:Reporting['Users']['Variables']['PasswordLastDays60']++ } else { $Script:Reporting['Users']['Variables']['PasswordLastDaysRecent']++ } if ($User.LastLogonDays -gt 360) { $Script:Reporting['Users']['Variables']['LastLogonDays360']++ } elseif ($User.LastLogonDays -gt 300) { $Script:Reporting['Users']['Variables']['LastLogonDays300']++ } elseif ($User.LastLogonDays -gt 180) { $Script:Reporting['Users']['Variables']['LastLogonDays180']++ } elseif ($User.LastLogonDays -gt 90) { $Script:Reporting['Users']['Variables']['LastLogonDays90']++ } elseif ($User.LastLogonDays -gt 60) { $Script:Reporting['Users']['Variables']['LastLogonDays60']++ } else { $Script:Reporting['Users']['Variables']['LastLogonDaysRecent']++ } } else { $Script:Reporting['Users']['Variables']['UsersDisabled']++ } if ($User.OwnerType -notin "WellKnownAdministrative", 'Administrative') { $Script:Reporting['Users']['Variables']['OwnerNotAdministrative']++ } else { $Script:Reporting['Users']['Variables']['OwnerAdministrative']++ } $Script:Reporting['Users']['Variables'].PasswordPolicies[$User.PasswordPolicyName]++ } } } Variables = @{ PasswordPolicies = [ordered] @{} } Summary = { New-HTMLText -Text @( "This report focuses on showing status of all users objects in the Active Directory forest. " "It shows how many users are enabled, disabled, expired, etc." ) -FontSize 10pt -LineBreak New-HTMLText -Text "Here's an overview of some statistics about users:" -FontSize 10pt New-HTMLList { New-HTMLListItem -Text "Total number of users: ", $($Script:Reporting['Users']['Variables'].UsersTotal) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of enabled users: ", $($Script:Reporting['Users']['Variables'].UsersEnabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of disabled users: ", $($Script:Reporting['Users']['Variables'].UsersDisabled) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of owners that are Domain Admins/Enterprise Admins: ", $($Script:Reporting['Users']['Variables'].OwnerAdministrative) -Color None, BlueMarguerite -FontWeight normal, bold New-HTMLListItem -Text "Total number of owenrs that are non-administrative: ", $($Script:Reporting['Users']['Variables'].OwnerNotAdministrative) -Color None, BlueMarguerite -FontWeight normal, bold foreach ($PasswordPolicy in $Script:Reporting['Users']['Variables'].PasswordPolicies.Keys) { $Number = $Script:Reporting['Users']['Variables'].PasswordPolicies[$PasswordPolicy] New-HTMLListItem -Text "Total number of users with password policy '$PasswordPolicy': ", $Number -Color None, BlueMarguerite -FontWeight normal, bold } } -FontSize 10pt } Solution = { if ($Script:Reporting['Users']['Data'] -is [System.Collections.IDictionary]) { New-HTMLSection -Invisible { New-HTMLPanel { $Script:Reporting['Users']['Summary'] } New-HTMLPanel { New-HTMLChart { New-ChartBarOptions -Type bar New-ChartLegend -Name 'Users by Password Policies' -Color SpringGreen, Salmon foreach ($PasswordPolicy in $Script:Reporting['Users']['Variables'].PasswordPolicies.Keys) { New-ChartBar -Name $PasswordPolicy -Value $Script:Reporting['Users']['Variables']['PasswordPolicies'][$PasswordPolicy] } New-ChartAxisY -LabelMaxWidth 300 -Show } -Title 'Users by Password Policies' -TitleAlignment center } } New-HTMLSection -HeaderText 'General statistics' -CanCollapse { New-HTMLPanel { New-HTMLChart { New-ChartPie -Name 'Users Enabled' -Value $Script:Reporting['Users']['Variables'].UsersEnabled -Color '#58ffc5' New-ChartPie -Name 'Users Disabled' -Value $Script:Reporting['Users']['Variables'].UsersDisabled -Color CoralRed } -Title "Enabled vs Disabled All User Objects" } New-HTMLPanel { New-HTMLChart { New-ChartPie -Name 'Administrative' -Value $Script:Reporting['Users']['Variables'].OwnerAdministrative -Color '#58ffc5' New-ChartPie -Name 'Other' -Value $Script:Reporting['Users']['Variables'].OwnerNotAdministrative -Color CoralRed } -Title "Owner being Administrative vs Other" } New-HTMLPanel { New-HTMLChart { New-ChartPie -Name 'Password Never Expires' -Value $Script:Reporting['Users']['Variables'].PasswordNeverExpires -Color CoralRed New-ChartPie -Name 'Password Expires' -Value $Script:Reporting['Users']['Variables'].PasswordExpires -Color '#58ffc5' } -Title "Password Never Expires vs Expires" -SubTitle 'Enabled Only' } } 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 { # highlight whole row as blue if the computer is disabled New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $false -Row -BackgroundColor LightYellow # highlight enabled column as red if the computer is disabled New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $false -BackgroundColor Salmon # highlight enabled column as BrightTurquoise if the computer is enabled # we don't know if it's any good, but lets try it New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $true -BackgroundColor BrightTurquoise # highlight whole row as green if the computer is enabled and LastLogon, PasswordDays Over 30 New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator le -Value 30 New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator le -Value 30 } -BackgroundColor PaleGreen -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 30 New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType string -Operator eq -Value '' } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType string -Operator eq -Value '' New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator gt -Value 30 } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType string -Operator eq -Value '' New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType string -Operator eq -Value '' } -BackgroundColor LightPink -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled # highlight whole row as green if the computer is enabled and LastLogon, PasswordDays Over 30 New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -Operator gt -Value 30 New-HTMLTableCondition -Name 'PasswordLastDays' -ComparisonType number -Operator gt -Value 30 } -BackgroundColor Salmon -HighlightHeaders LastLogonDays, PasswordLastDays, Enabled New-HTMLTableConditionGroup -Conditions { New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -Operator eq -Value $True New-HTMLTableCondition -Name 'PasswordNotRequired' -ComparisonType string -Operator eq -Value $True } -BackgroundColor Red -HighlightHeaders Name, SamAccountName, Enabled, PasswordNotRequired } -ScrollX } } } } } } function Convert-TrustForestTrustInfo { [CmdletBinding()] param( [byte[]] $msDSTrustForestTrustInfo ) # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/66387402-cb2b-490c-bf2a-f4ad687397e4 $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') { # ForestTrustTopLevelName, ForestTrustTopLevelNameEx 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 ConvertFrom-SimplifiedDelegation { <# .SYNOPSIS Experimental way to define permissions that are prepopulated .DESCRIPTION Experimental way to define permissions that are prepopulated .PARAMETER Principal Principal to apply the permission to .PARAMETER SimplifiedDelegation Simplified delegation to apply .PARAMETER AccessControlType Access control type .PARAMETER InheritanceType Inheritance type, if not specified, it will be set to Descendents .PARAMETER OneLiner If specified, the output will be in one line, rather than a multilevel object .EXAMPLE ConvertFrom-SimplifiedDelegation -Principal $ConvertedPrincipal -SimplifiedDelegation $SimplifiedDelegation -OneLiner:$OneLiner.IsPresent -AccessControlType $AccessControlType -InheritanceType $InheritanceType .NOTES General notes #> [CmdletBinding()] param( [string] $Principal, [string[]] $SimplifiedDelegation, [System.Security.AccessControl.AccessControlType] $AccessControlType, [alias('ActiveDirectorySecurityInheritance')][nullable[System.DirectoryServices.ActiveDirectorySecurityInheritance]] $InheritanceType, [switch] $OneLiner ) # Remember to change SimplifiedDelegationDefinitionList below!!! $Script:SimplifiedDelegationDefinition = [ordered] @{ ComputerDomainJoin = @( # allows only to join computers to domain, but not rejoin or move if (-not $InheritanceType) { $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendents } ConvertTo-Delegation -ConvertedPrincipal $Principal -AccessControlType $AccessControlType -AccessRule 'CreateChild' -InheritanceType $InheritanceType -InheritedObjectType 'Computer' -OneLiner:$OneLiner ) ComputerDomainReJoin = @( # allows to join computers to domain, but also rejoin them on demand if (-not $InheritanceType) { $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendents } ConvertTo-Delegation -ConvertedPrincipal $Principal -AccessControlType $AccessControlType -AccessRule 'CreateChild', 'DeleteChild' -InheritanceType $InheritanceType -InheritedObjectType 'Computer' -OneLiner:$OneLiner ConvertTo-Delegation -ConvertedPrincipal $Principal -AccessControlType $AccessControlType -AccessRule 'ExtendedRight' -ObjectType 'Reset Password' -InheritanceType $InheritanceType -InheritedObjectType 'Computer' -OneLiner:$OneLiner ConvertTo-Delegation -ConvertedPrincipal $Principal -AccessControlType $AccessControlType -AccessRule 'ExtendedRight' -ObjectType 'Account Restrictions' -InheritanceType $InheritanceType -InheritedObjectType 'Computer' -OneLiner:$OneLiner ConvertTo-Delegation -ConvertedPrincipal $Principal -AccessControlType $AccessControlType -AccessRule 'ExtendedRight' -ObjectType 'Validated write to DNS host name' -InheritanceType $InheritanceType -InheritedObjectType 'Computer' -OneLiner:$OneLiner ConvertTo-Delegation -ConvertedPrincipal $Principal -AccessControlType $AccessControlType -AccessRule 'ExtendedRight' -ObjectType 'Validated write to service principal name' -InheritanceType $InheritanceType -InheritedObjectType 'Computer' -OneLiner:$OneLiner ) FullControl = @( if (-not $InheritanceType) { $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All } ConvertTo-Delegation -ConvertedPrincipal $Principal -AccessControlType $AccessControlType -AccessRule 'GenericAll' -InheritanceType $InheritanceType -OneLiner:$OneLiner ) } foreach ($Simple in $SimplifiedDelegation) { $Script:SimplifiedDelegationDefinition[$Simple] } } $Script:SimplifiedDelegationDefinitionList = @( 'ComputerDomainJoin' 'ComputerDomainReJoin' 'FullControl' ) [scriptblock] $ConvertSimplifiedDelegationDefinition = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $Script:SimplifiedDelegationDefinitionList | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" } } Register-ArgumentCompleter -CommandName ConvertFrom-SimplifiedDelegation -ParameterName SimplifiedDelegation -ScriptBlock $ConvertSimplifiedDelegationDefinition function ConvertTo-ComputerFQDN { [cmdletBinding()] param( [string] $Computer ) # Checks for ServerName - Makes sure to convert IPAddress to DNS, otherwise SSL won't work $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 ConvertTo-Delegation { [CmdletBinding()] param( [string] $Principal, [System.DirectoryServices.ActiveDirectoryRights] $AccessRule, [System.Security.AccessControl.AccessControlType] $AccessControlType, [alias('ObjectTypeName')][string] $ObjectType, [alias('InheritedObjectTypeName')][string] $InheritedObjectType, [alias('ActiveDirectorySecurityInheritance')][nullable[System.DirectoryServices.ActiveDirectorySecurityInheritance]] $InheritanceType, [switch] $OneLiner ) if ($OneLiner) { [PSCustomObject] @{ Principal = $Principal ActiveDirectoryRights = $AccessRule AccessControlType = $AccessControlType ObjectTypeName = $ObjectType InheritedObjectTypeName = $InheritedObjectType InheritanceType = $InheritanceType } } else { [PSCustomObject] @{ Principal = $Principal Permissions = [PSCustomObject] @{ 'ActiveDirectoryRights' = $AccessRule 'AccessControlType' = $AccessControlType 'ObjectTypeName' = $ObjectType 'InheritedObjectTypeName' = $InheritedObjectType 'InheritanceType' = $InheritanceType } } } } [scriptblock] $ConvertToDelegationAutocompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) if (-not $Script:ADSchemaGuids) { Import-Module ActiveDirectory -Verbose:$false $Script:ADSchemaGuids = Convert-ADSchemaToGuid } $Script:ADSchemaGuids.Keys | Where-Object { $_ -like "*$wordToComplete*" } | ForEach-Object { "'$($_)'" } #| Sort-Object } Register-ArgumentCompleter -CommandName ConvertTo-Delegation -ParameterName ObjectType -ScriptBlock $ConvertToDelegationAutocompleter Register-ArgumentCompleter -CommandName ConvertTo-Delegation -ParameterName InheritedObjectType -ScriptBlock $ConvertToDelegationAutocompleter function ConvertTo-TimeSpanFromRepadmin { [cmdletBinding()] param ( [Parameter(Mandatory)][string]$timeString ) switch -Regex ($timeString) { '^\s*(\d+)d\.(\d+)h:(\d+)m:(\d+)s\s*$' { $days = $Matches[1] $hours = $Matches[2] $minutes = $Matches[3] $seconds = $Matches[4] New-TimeSpan -Days $days -Hours $hours -Minutes $minutes -Seconds $seconds } '^\s*(\d+)h:(\d+)m:(\d+)s\s*$' { $hours = $Matches[1] $minutes = $Matches[2] $seconds = $Matches[3] New-TimeSpan -Hours $hours -Minutes $minutes -Seconds $seconds } '^\s*(\d+)m:(\d+)s\s*$' { $minutes = $Matches[1] $seconds = $Matches[2] New-TimeSpan -Minutes $minutes -Seconds $seconds } '^\s*:(\d+)s\s*$' { $seconds = $Matches[1] New-TimeSpan -Seconds $seconds } '^\s*(\d+)s\s*$' { $seconds = $Matches[1] New-TimeSpan -Seconds $seconds } '^>60 days\s*$' { New-TimeSpan -Days 60 } '^\s*\(unknown\)\s*$' { $null } default { $null } } } 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') { # IPv4 $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 { # IPv6 $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 # Ipv6 doesn't have a subnet mask 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 # Ipv6 doesn't have a subnet mask TotalHosts = $AddressRange.TotalHosts UsableHosts = $AddressRange.UsableHosts HostMin = $AddressRange.HostMin HostMax = $AddressRange.HostMax Broadcast = $AddressRange.Broadcast } } } } } function Get-FilteredACL { [cmdletBinding()] param( [System.DirectoryServices.ActiveDirectoryAccessRule] $ACL, [alias('ResolveTypes')][switch] $Resolve, [string] $Principal, [switch] $Inherited, [switch] $NotInherited, [System.Security.AccessControl.AccessControlType] $AccessControlType, [Alias('ObjectTypeName')][string[]] $IncludeObjectTypeName, [Alias('InheritedObjectTypeName')][string[]] $IncludeInheritedObjectTypeName, [string[]] $ExcludeObjectTypeName, [string[]] $ExcludeInheritedObjectTypeName, [Alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRights, [System.DirectoryServices.ActiveDirectoryRights[]] $ExcludeActiveDirectoryRights, [Alias('InheritanceType', 'IncludeInheritanceType')][System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $IncludeActiveDirectorySecurityInheritance, [Alias('ExcludeInheritanceType')][System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $ExcludeActiveDirectorySecurityInheritance, [PSCustomObject] $PrincipalRequested, [switch] $Bundle ) [Array] $ADRights = $ACL.ActiveDirectoryRights -split ', ' if ($AccessControlType) { if ($ACL.AccessControlType -ne $AccessControlType) { continue } } if ($Inherited) { if ($ACL.IsInherited -eq $false) { # if it's not inherited and we require inherited lets continue 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 $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 -Cache if (-not $IdentityResolve) { #Write-Verbose "Get-ADACL - Reverting to Convert-Identity for $IdentityReference" $ConvertIdentity = Convert-Identity -Identity $IdentityReference -Verbose:$false $ReturnObject['PrincipalType'] = $ConvertIdentity.Type # it's not really foreignSecurityPrincipal but can't tell what it is... # https://superuser.com/questions/1067246/is-nt-authority-system-a-user-or-a-group $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 } # We compare principal to real principal based on Resolve, we compare both PrincipalName and SID to cover our ground if ($PrincipalRequested -and $PrincipalRequested.SID -ne $ReturnObject['PrincipalObjectSid']) { continue } } else { # We compare principal to principal as returned without resolve if ($Principal -and $Principal -ne $IdentityReference) { continue } } $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 } 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-PrivateACL { <# .SYNOPSIS Get ACL from AD Object .DESCRIPTION Get ACL from AD Object .PARAMETER ADObject AD Object to get ACL from .PARAMETER FullObject Returns full object instead of just ACL .EXAMPLE Get-PrivateACL -ADObject 'DC=ad,DC=evotec,DC=xyz' .NOTES General notes #> [cmdletBinding()] param( [parameter(Mandatory)][alias('DistinguishedName')][string] $ADObject, [switch] $FullObject ) try { $ADObjectData = Get-ADObject -Identity $ADObject -Properties ntSecurityDescriptor, CanonicalName -ErrorAction Stop } catch { Write-Warning -Message "Get-PrivateACL - Unable to get ADObject data for $ADObject. Error: $($_.Exception.Message)" return } if ($FullObject) { $ADObjectData } else { $ADObjectData.ntSecurityDescriptor } } function Get-ProtocolStatus { <# .SYNOPSIS Translates registry of protocol to status .DESCRIPTION Translates registry of protocol to status .PARAMETER RegistryEntry Accepts registry entry from Get-PSRegistry .EXAMPLE $Client = Get-PSRegistry -ComputerName 'AD1' -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Client' Get-ProtocolStatus -RegistryEntry $Client .NOTES When DisabledByDefault flag is set to 1, SSL / TLS version X is not used by default. If an SSPI app requests to use this version of SSL / TLS, it will be negotiated. In a nutshell, SSL is not disabled when you use DisabledByDefault flag. When Enabled flag is set to 0, SSL / TLS version X is disabled and cannot be nagotiated by any SSPI app (even if DisabledByDefault flag is set to 0). #> [CmdletBinding()] param( [PSCustomObject] $RegistryEntry, [string] $WindowsVersion, [System.Collections.IDictionary] $ProtocolDefaults, [string] $Protocol ) if ($Protocol) { $Default = $ProtocolDefaults[$Protocol] if ($Default -eq 'Not supported') { return $Default } } else { Write-Warning -Message "Get-ProtocolStatus - protocol not specified." } if ($RegistryEntry.PSConnection -eq $true) { if ($RegistryEntry.PSError -eq $true) { #$Status = 'Not set, enabled' $Status = 'Enabled' } else { if ($RegistryEntry.DisabledByDefault -eq 0 -and $RegistryEntry.Enabled -eq 1) { $Status = 'Enabled' } elseif ($RegistryEntry.DisabledByDefault -eq 1 -and $RegistryEntry.Enabled -eq 0) { $Status = 'Disabled' } elseif ($RegistryEntry.DisabledByDefault -eq 1 -and $RegistryEntry.Enabled -eq 1) { $Status = 'Enabled' } elseif ($RegistryEntry.DisabledByDefault -eq 0 -and $RegistryEntry.Enabled -eq 0) { $Status = 'Disabled' } elseif ($RegistryEntry.DisabledByDefault -eq 0) { $Status = 'Enabled' } elseif ($RegistryEntry.DisabledByDefault -eq 1) { $Status = 'DisabledDefault' } elseif ($RegistryEntry.Enabled -eq 1) { $Status = 'Enabled' } elseif ($RegistryEntry.Enabled -eq 0) { $Status = 'Disabled' } else { $Status = 'Wont happen' } } } else { $Status = 'No connection' } $Status } 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 = @( #@{ Name = 'Root'; Value = $RootDomainNamingContext } 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 #'ObjectType' = $ACL.ObjectType #'InheritedObjectType' = $ACL.InheritedObjectType '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] @{} # https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.activedirectory.trusttype?view=dotnet-plat-ext-3.1 $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.' # 2 External = 'The trust relationship is with a domain outside of the current forest.' # 3 Forest = 'The trust relationship is between two forest root domains in separate Windows Server 2003 forests.' # 4 Kerberos = 'The trusted domain is an MIT Kerberos realm.' # 5 ParentChild = 'The trust relationship is between a parent and a child domain.' # 1 TreeRoot = 'One of the domains in the trust relationship is a tree root.' # 0 Unknown = 'The trust is a non-specific type.' #6 } # https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.activedirectory.trustdirection?view=dotnet-plat-ext-3.1 $TrustDirection = @{ Bidirectional = 'Each domain or forest has access to the resources of the other domain or forest.' # 3 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.' # 1 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.' # 2 } 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 #'LDAP://DC=TEST,DC=EVOTEC,DC=PL' $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] @{ #Name = [string] $Trust.properties.name # {ad.evotec.xyz} TrustSource = $TrustSource TrustPartner = [string] $Trust.properties.trustpartner # {ad.evotec.xyz} TrustPartnerNetBios = [string] $Trust.properties.flatname # {EVOTEC} TrustDirection = $TrustD.ToString() # {3} TrustType = $TrustT.ToString() # {2} TrustAttributes = $TrustAttributes # {32} TrustDirectionText = $TrustDirection[$TrustD.ToString()] TrustTypeText = $TrustType[$TrustT.ToString()] WhenCreated = [DateTime] $Trust.properties.whencreated[0] # {26.07.2018 10:59:52} WhenChanged = [DateTime] $Trust.properties.whenchanged[0] # {14.08.2020 22:23:14} ObjectSID = $ObjectSID Distinguishedname = [string] $Trust.properties.distinguishedname # {CN=ad.evotec.xyz,CN=System,DC=ad,DC=evotec,DC=pl} IsCriticalSystemObject = [bool]::Parse($Trust.properties.iscriticalsystemobject[0]) # {True} ObjectGuid = [guid]::new($Trust.properties.objectguid[0]) ObjectCategory = [string] $Trust.properties.objectcategory # {CN=Trusted-Domain,CN=Schema,CN=Configuration,DC=ad,DC=evotec,DC=xyz} ObjectClass = ([array] $Trust.properties.objectclass)[-1] # {top, leaf, trustedDomain} UsnCreated = [string] $Trust.properties.usncreated # {14149} UsnChanged = [string] $Trust.properties.usnchanged # {4926091} ShowInAdvancedViewOnly = [bool]::Parse($Trust.properties.showinadvancedviewonly) # {True} TrustPosixOffset = [string] $Trust.properties.trustposixoffset # {-2147483648} msDSTrustForestTrustInfo = $msDSTrustForestTrustInfo msDSSupportedEncryptionTypes = if ($Trust.properties.'msds-supportedencryptiontypes') { Get-ADEncryptionTypes -Value ([int] $Trust.properties.'msds-supportedencryptiontypes'[0]) } else { $null } #SecurityIdentifier = [string] $Trust.properties.securityidentifier # {1 4 0 0 0 0 0 5 21 0 0 0 113 37 225 50 27 133 23 171 67 175 144 188} #InstanceType = $Trust.properties.instancetype # {4} #AdsPath = [string] $Trust.properties.adspath # {LDAP://CN=ad.evotec.xyz,CN=System,DC=ad,DC=evotec,DC=pl} #CN = [string] $Trust.properties.cn # {ad.evotec.xyz} #ObjectGuid = $Trust.properties.objectguid # {193 58 187 220 218 30 146 77 162 218 90 74 159 98 153 219} #dscorepropagationdata = $Trust.properties.dscorepropagationdata # {01.01.1601 00:00:00} } if ($AsHashTable) { $Summary[$TrustObject.trustpartner] = $TrustObject } else { $TrustObject } } if ($AsHashTable) { $Summary } } function Get-WinDnsRootHint { [CmdLetBinding()] param( [string[]] $ComputerName, [string] $Domain = $ENV:USERDNSDOMAIN ) if ($Domain -and -not $ComputerName) { $ComputerName = (Get-ADDomainController -Filter * -Server $Domain).HostName } foreach ($Computer in $ComputerName) { $ServerRootHints = Get-DnsServerRootHint -ComputerName $Computer foreach ($_ in $ServerRootHints.IPAddress) { [PSCustomObject] @{ DistinguishedName = $_.DistinguishedName HostName = $_.HostName RecordClass = $_.RecordClass IPv4Address = $_.RecordData.IPv4Address.IPAddressToString IPv6Address = $_.RecordData.IPv6Address.IPAddressToString #RecordData = $_.RecordData.IPv4Address -join ', ' #RecordData1 = $_.RecordData RecordType = $_.RecordType Timestamp = $_.Timestamp TimeToLive = $_.TimeToLive Type = $_.Type GatheredFrom = $Computer } } } } function Get-WinDnsServerCache { [CmdLetBinding()] param( [string[]] $ComputerName, [string] $Domain = $ENV:USERDNSDOMAIN ) if ($Domain -and -not $ComputerName) { $ComputerName = (Get-ADDomainController -Filter * -Server $Domain).HostName } foreach ($Computer in $ComputerName) { $DnsServerCache = Get-DnsServerCache -ComputerName $Computer foreach ($_ in $DnsServerCache) { [PSCustomObject] @{ DistinguishedName = $_.DistinguishedName IsAutoCreated = $_.IsAutoCreated IsDsIntegrated = $_.IsDsIntegrated IsPaused = $_.IsPaused IsReadOnly = $_.IsReadOnly IsReverseLookupZone = $_.IsReverseLookupZone IsShutdown = $_.IsShutdown ZoneName = $_.ZoneName ZoneType = $_.ZoneType EnablePollutionProtection = $_.EnablePollutionProtection IgnorePolicies = $_.IgnorePolicies LockingPercent = $_.LockingPercent MaxKBSize = $_.MaxKBSize MaxNegativeTtl = $_.MaxNegativeTtl MaxTtl = $_.MaxTtl GatheredFrom = $Computer } } } } function Get-WinDnsServerDiagnostics { [CmdLetBinding()] param( [string] $ComputerName ) $DnsServerDiagnostics = Get-DnsServerDiagnostics -ComputerName $ComputerName foreach ($_ in $DnsServerDiagnostics) { [PSCustomObject] @{ FilterIPAddressList = $_.FilterIPAddressList Answers = $_.Answers EnableLogFileRollover = $_.EnableLogFileRollover EnableLoggingForLocalLookupEvent = $_.EnableLoggingForLocalLookupEvent EnableLoggingForPluginDllEvent = $_.EnableLoggingForPluginDllEvent EnableLoggingForRecursiveLookupEvent = $_.EnableLoggingForRecursiveLookupEvent EnableLoggingForRemoteServerEvent = $_.EnableLoggingForRemoteServerEvent EnableLoggingForServerStartStopEvent = $_.EnableLoggingForServerStartStopEvent EnableLoggingForTombstoneEvent = $_.EnableLoggingForTombstoneEvent EnableLoggingForZoneDataWriteEvent = $_.EnableLoggingForZoneDataWriteEvent EnableLoggingForZoneLoadingEvent = $_.EnableLoggingForZoneLoadingEvent EnableLoggingToFile = $_.EnableLoggingToFile EventLogLevel = $_.EventLogLevel FullPackets = $_.FullPackets LogFilePath = $_.LogFilePath MaxMBFileSize = $_.MaxMBFileSize Notifications = $_.Notifications Queries = $_.Queries QuestionTransactions = $_.QuestionTransactions ReceivePackets = $_.ReceivePackets SaveLogsToPersistentStorage = $_.SaveLogsToPersistentStorage SendPackets = $_.SendPackets TcpPackets = $_.TcpPackets UdpPackets = $_.UdpPackets UnmatchedResponse = $_.UnmatchedResponse Update = $_.Update UseSystemEventLog = $_.UseSystemEventLog WriteThrough = $_.WriteThrough GatheredFrom = $ComputerName } } } function Get-WinDnsServerDirectoryPartition { [CmdLetBinding()] param( [string] $ComputerName, [string] $Splitter ) $DnsServerDirectoryPartition = Get-DnsServerDirectoryPartition -ComputerName $ComputerName foreach ($_ in $DnsServerDirectoryPartition) { [PSCustomObject] @{ DirectoryPartitionName = $_.DirectoryPartitionName CrossReferenceDistinguishedName = $_.CrossReferenceDistinguishedName DirectoryPartitionDistinguishedName = $_.DirectoryPartitionDistinguishedName Flags = $_.Flags Replica = if ($Splitter -ne '') { $_.Replica -join $Splitter } else { $_.Replica } State = $_.State ZoneCount = $_.ZoneCount GatheredFrom = $ComputerName } } } function Get-WinDnsServerDsSetting { [CmdLetBinding()] param( [string] $ComputerName ) $DnsServerDsSetting = Get-DnsServerDsSetting -ComputerName $ComputerName foreach ($_ in $DnsServerDsSetting) { [PSCustomObject] @{ DirectoryPartitionAutoEnlistInterval = $_.DirectoryPartitionAutoEnlistInterval LazyUpdateInterval = $_.LazyUpdateInterval MinimumBackgroundLoadThreads = $_.MinimumBackgroundLoadThreads RemoteReplicationDelay = $_.RemoteReplicationDelay TombstoneInterval = $_.TombstoneInterval GatheredFrom = $ComputerName } } } function Get-WinDnsServerEDns { [CmdLetBinding()] param( [string] $ComputerName ) $DnsServerDsSetting = Get-DnsServerEDns -ComputerName $ComputerName foreach ($_ in $DnsServerDsSetting) { [PSCustomObject] @{ CacheTimeout = $_.CacheTimeout EnableProbes = $_.EnableProbes EnableReception = $_.EnableReception GatheredFrom = $ComputerName } } } function Get-WinDnsServerGlobalNameZone { [CmdLetBinding()] param( [string[]] $ComputerName, [string] $Domain = $ENV:USERDNSDOMAIN ) if ($Domain -and -not $ComputerName) { $ComputerName = (Get-ADDomainController -Filter * -Server $Domain).HostName } foreach ($Computer in $ComputerName) { $DnsServerGlobalNameZone = Get-DnsServerGlobalNameZone -ComputerName $Computer foreach ($_ in $DnsServerGlobalNameZone) { [PSCustomObject] @{ AlwaysQueryServer = $_.AlwaysQueryServer BlockUpdates = $_.BlockUpdates Enable = $_.Enable EnableEDnsProbes = $_.EnableEDnsProbes GlobalOverLocal = $_.GlobalOverLocal PreferAaaa = $_.PreferAaaa SendTimeout = $_.SendTimeout ServerQueryInterval = $_.ServerQueryInterval GatheredFrom = $Computer } } } } #Get-WinDnsServerGlobalNameZone -ComputerName 'AD1' function Get-WinDnsServerGlobalQueryBlockList { [CmdLetBinding()] param( [string[]] $ComputerName, [string] $Domain = $ENV:USERDNSDOMAIN, [switch] $Formatted, [string] $Splitter = ', ' ) if ($Domain -and -not $ComputerName) { $ComputerName = (Get-ADDomainController -Filter * -Server $Domain).HostName } foreach ($Computer in $ComputerName) { $ServerGlobalQueryBlockList = Get-DnsServerGlobalQueryBlockList -ComputerName $Computer foreach ($_ in $ServerGlobalQueryBlockList) { if ($Formatted) { [PSCustomObject] @{ Enable = $_.Enable List = $_.List -join $Splitter GatheredFrom = $Computer } } else { [PSCustomObject] @{ Enable = $_.Enable List = $_.List GatheredFrom = $Computer } } } } } function Get-WinDnsServerRecursion { [CmdLetBinding()] param( [string[]] $ComputerName, [string] $Domain = $ENV:USERDNSDOMAIN ) if ($Domain -and -not $ComputerName) { $ComputerName = (Get-ADDomainController -Filter * -Server $Domain).HostName } foreach ($Computer in $ComputerName) { $DnsServerRecursion = Get-DnsServerRecursion -ComputerName $Computer foreach ($_ in $DnsServerRecursion) { [PSCustomObject] @{ AdditionalTimeout = $_.AdditionalTimeout Enable = $_.Enable RetryInterval = $_.RetryInterval SecureResponse = $_.SecureResponse Timeout = $_.Timeout GatheredFrom = $Computer } } } } function Get-WinDnsServerRecursionScope { [CmdLetBinding()] param( [string[]] $ComputerName, [string] $Domain = $ENV:USERDNSDOMAIN ) if ($Domain -and -not $ComputerName) { $ComputerName = (Get-ADDomainController -Filter * -Server $Domain).HostName } foreach ($Computer in $ComputerName) { $DnsServerRecursionScope = Get-DnsServerRecursionScope -ComputerName $Computer foreach ($_ in $DnsServerRecursionScope) { [PSCustomObject] @{ Name = $_.Name Forwarder = $_.Forwarder EnableRecursion = $_.EnableRecursion GatheredFrom = $Computer } } } } function Get-WinDnsServerResponseRateLimiting { [CmdLetBinding()] param( [string[]] $ComputerName, [string] $Domain = $ENV:USERDNSDOMAIN ) if ($Domain -and -not $ComputerName) { $ComputerName = (Get-ADDomainController -Filter * -Server $Domain).HostName } foreach ($Computer in $ComputerName) { $DnsServerResponseRateLimiting = Get-DnsServerResponseRateLimiting -ComputerName $Computer foreach ($_ in $DnsServerResponseRateLimiting) { [PSCustomObject] @{ ResponsesPerSec = $_.ResponsesPerSec ErrorsPerSec = $_.ErrorsPerSec WindowInSec = $_.WindowInSec IPv4PrefixLength = $_.IPv4PrefixLength IPv6PrefixLength = $_.IPv6PrefixLength LeakRate = $_.LeakRate TruncateRate = $_.TruncateRate MaximumResponsesPerWindow = $_.MaximumResponsesPerWindow Mode = $_.Mode GatheredFrom = $Computer } } } } function Get-WinDnsServerSettings { [CmdLetBinding()] param( [string] $ComputerName ) <# ComputerName : AD1.ad.evotec.xyz MajorVersion : 10 MinorVersion : 0 BuildNumber : 14393 IsReadOnlyDC : False EnableDnsSec : False EnableIPv6 : True EnableOnlineSigning : True NameCheckFlag : 2 AddressAnswerLimit : 0 XfrConnectTimeout(s) : 30 BootMethod : 3 AllowUpdate : True UpdateOptions : 783 DsAvailable : True DisableAutoReverseZone : False AutoCacheUpdate : False RoundRobin : True LocalNetPriority : True StrictFileParsing : False LooseWildcarding : False BindSecondaries : False WriteAuthorityNS : False ForwardDelegations : False AutoConfigFileZones : 1 EnableDirectoryPartitions : True RpcProtocol : 5 EnableVersionQuery : 0 EnableDuplicateQuerySuppression : True LameDelegationTTL : 00:00:00 AutoCreateDelegation : 2 AllowCnameAtNs : True RemoteIPv4RankBoost : 5 RemoteIPv6RankBoost : 0 EnableRsoForRodc : True MaximumRodcRsoQueueLength : 300 MaximumRodcRsoAttemptsPerCycle : 100 OpenAclOnProxyUpdates : True NoUpdateDelegations : False EnableUpdateForwarding : False MaxResourceRecordsInNonSecureUpdate : 30 EnableWinsR : True LocalNetPriorityMask : 255 DeleteOutsideGlue : False AppendMsZoneTransferTag : False AllowReadOnlyZoneTransfer : False MaximumUdpPacketSize : 4000 TcpReceivePacketSize : 65536 EnableSendErrorSuppression : True SelfTest : 4294967295 XfrThrottleMultiplier : 10 SilentlyIgnoreCnameUpdateConflicts : False EnableIQueryResponseGeneration : False SocketPoolSize : 2500 AdminConfigured : True SocketPoolExcludedPortRanges : {} ForestDirectoryPartitionBaseName : ForestDnsZones DomainDirectoryPartitionBaseName : DomainDnsZones ServerLevelPluginDll : EnableRegistryBoot : PublishAutoNet : False QuietRecvFaultInterval(s) : 0 QuietRecvLogInterval(s) : 0 ReloadException : False SyncDsZoneSerial : 2 EnableDuplicateQuerySuppression : True SendPort : Random MaximumSignatureScanPeriod : 2.00:00:00 MaximumTrustAnchorActiveRefreshInterval : 15.00:00:00 ListeningIPAddress : {192.168.240.189} AllIPAddress : {192.168.240.189} ZoneWritebackInterval : 00:01:00 RootTrustAnchorsURL : https://data.iana.org/root-anchors/root-anchors.xml ScopeOptionValue : 0 IgnoreServerLevelPolicies : False IgnoreAllPolicies : False VirtualizationInstanceOptionValue : 0 #> $DnsServerSetting = Get-DnsServerSetting -ComputerName $ComputerName -All foreach ($_ in $DnsServerSetting) { [PSCustomObject] @{ AllIPAddress = $_.AllIPAddress ListeningIPAddress = $_.ListeningIPAddress BuildNumber = $_.BuildNumber ComputerName = $_.ComputerName EnableDnsSec = $_.EnableDnsSec EnableIPv6 = $_.EnableIPv6 IsReadOnlyDC = $_.IsReadOnlyDC MajorVersion = $_.MajorVersion MinorVersion = $_.MinorVersion GatheredFrom = $ComputerName } } } #Get-WinDnsServerSettings -ComputerName 'AD1' #Get-DnsServerSetting -ComputerName AD1 -All function Get-WinDnsServerVirtualizationInstance { [CmdLetBinding()] param( [string] $ComputerName ) $DnsServerVirtualizationInstance = Get-DnsServerVirtualizationInstance -ComputerName $ComputerName foreach ($_ in $DnsServerVirtualizationInstance) { [PSCustomObject] @{ VirtualizationInstance = $_.VirtualizationInstance FriendlyName = $_.FriendlyName Description = $_.Description GatheredFrom = $ComputerName } } } $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-ActiveDirectoryAccessRule { [CmdletBinding()] param( $ActiveDirectoryAccessRule, $ObjectType, $InheritanceType, $InheritedObjectType, $AccessControlType, $AccessRule, $Identity ) try { if ($ActiveDirectoryAccessRule) { $AccessRuleToAdd = $ActiveDirectoryAccessRule } elseif ($ObjectType -and $InheritanceType -and $InheritedObjectType) { $ObjectTypeGuid = Convert-ADSchemaToGuid -SchemaName $ObjectType $InheritedObjectTypeGuid = Convert-ADSchemaToGuid -SchemaName $InheritedObjectType if ($ObjectTypeGuid -and $InheritedObjectTypeGuid) { $AccessRuleToAdd = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType, $ObjectTypeGuid, $InheritanceType, $InheritedObjectTypeGuid) } else { if (-not $ObjectTypeGuid -and -not $InheritedObjectTypeGuid) { Write-Warning "Add-PrivateACL - Object type '$ObjectType' or '$InheritedObjectType' not found in schema" } elseif (-not $ObjectTypeGuid) { Write-Warning "Add-PrivateACL - Object type '$ObjectType' not found in schema" } else { Write-Warning "Add-PrivateACL - Object type '$InheritedObjectType' not found in schema" } return } } elseif ($ObjectType -and $InheritanceType) { $ObjectTypeGuid = Convert-ADSchemaToGuid -SchemaName $ObjectType if ($ObjectTypeGuid) { $AccessRuleToAdd = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType, $ObjectTypeGuid, $InheritanceType) } else { Write-Warning "Add-PrivateACL - Object type '$ObjectType' not found in schema" return } } elseif ($ObjectType) { $ObjectTypeGuid = Convert-ADSchemaToGuid -SchemaName $ObjectType if ($ObjectTypeGuid) { $AccessRuleToAdd = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType, $ObjectTypeGuid) } else { Write-Warning "Add-PrivateACL - Object type '$ObjectType' not found in schema" return } } else { $AccessRuleToAdd = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType) } } catch { Write-Warning "Add-PrivateACL - Error creating ActiveDirectoryAccessRule: $_" return } $AccessRuleToAdd } 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)' { #if ($DataTableID) { # New-DiagramEvent -ID $DataTableID -ColumnID $ColumnID #} #New-DiagramOptionsLayout -HierarchicalEnabled $true -HierarchicalDirection FromLeftToRight #-HierarchicalSortMethod directed #New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 50 New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($ADGroup) { # Add it's members to diagram foreach ($ADObject in $ADGroup) { # Lets build our diagram #[int] $Level = $($ADObject.Nesting) + 1 $ID = "$($ADObject.DomainName)$($ADObject.DistinguishedName)" #[int] $LevelParent = $($ADObject.Nesting) $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroupDN)" 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 $Script:ConfigurationIcons.ImageUser } 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 = $Script:ConfigurationIcons.ImageGroup } else { $BorderColor = 'Blue' $Image = $Script:ConfigurationIcons.ImageGroupNested } $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 $Script:ConfigurationIcons.ImageComputer } 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 $Script:ConfigurationIcons.ImageOther } 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 #-HierarchicalDirection FromLeftToRight #-HierarchicalSortMethod directed New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200 #New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($ADGroup) { # Add it's members to diagram foreach ($ADObject in $ADGroup) { # Lets build our diagram [int] $Level = $($ADObject.Nesting) + 1 $ID = "$($ADObject.DomainName)$($ADObject.DistinguishedName)$Level" [int] $LevelParent = $($ADObject.Nesting) $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroupDN)$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 $Script:ConfigurationIcons.ImageUser -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 = $Script:ConfigurationIcons.ImageGroup $IconSolid = 'user-friends' } elseif ($ADObject.CircularIndirect -eq $true -or $ADObject.CircularDirect -eq $true) { $Image = $Script:ConfigurationIcons.ImageGroupCircular $BorderColor = 'PaleVioletRed' $IconSolid = 'circle-notch' } else { $BorderColor = 'VeryLightGrey' $Image = $Script:ConfigurationIcons.ImageGroupNested $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 $Script:ConfigurationIcons.ImageComputer -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 $Script:ConfigurationIcons.ImageOther -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)' { #if ($DataTableID) { # New-DiagramEvent -ID $DataTableID -ColumnID $ColumnID #} #New-DiagramOptionsLayout -HierarchicalEnabled $true -HierarchicalDirection FromLeftToRight #-HierarchicalSortMethod directed #New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 50 New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($ADGroup) { # Add it's members to diagram foreach ($ADObject in $ADGroup) { # Lets build our diagram # This diagram of Summary doesn't use level checking because it's a summary of a groups, and the level will be different per group # This means that it will look a bit different than what is there when comparing 1 to 1 with the other diagrams #[int] $Level = $($ADObject.Nesting) + 1 $ID = "$($ADObject.DomainName)$($ADObject.DistinguishedName)" #[int] $LevelParent = $($ADObject.Nesting) $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroupDN)" # We track connection for ID to make sure that only once the conenction is added 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 $Script:ConfigurationIcons.ImageUser } 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 = $Script:ConfigurationIcons.ImageGroup } else { $BorderColor = 'Blue' $Image = $Script:ConfigurationIcons.ImageGroupNested } $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 $Script:ConfigurationIcons.ImageComputer } 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 $Script:ConfigurationIcons.ImageOther } 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 #-HierarchicalDirection FromLeftToRight #-HierarchicalSortMethod directed New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200 #New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($ADGroup) { # Add it's members to diagram foreach ($ADObject in $ADGroup) { # Lets build our diagram # This diagram of Summary doesn't use level checking because it's a summary of a groups, and the level will be different per group # This means that it will look a bit different than what is there when comparing 1 to 1 with the other diagrams #[int] $Level = $($ADObject.Nesting) + 1 $ID = "$($ADObject.DomainName)$($ADObject.DistinguishedName)" #[int] $LevelParent = $($ADObject.Nesting) $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroupDN)" [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 $Script:ConfigurationIcons.ImageUser -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 = $Script:ConfigurationIcons.ImageGroup } else { $BorderColor = 'Blue' $Image = $Script:ConfigurationIcons.ImageGroupNested } $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 $Script:ConfigurationIcons.ImageComputer -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 $Script:ConfigurationIcons.ImageOther -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)' { #if ($DataTableID) { # New-DiagramEvent -ID $DataTableID -ColumnID $ColumnID #} #New-DiagramOptionsLayout -HierarchicalEnabled $true -HierarchicalDirection FromLeftToRight #-HierarchicalSortMethod directed #New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 50 New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($Identity) { # Add it's members to diagram foreach ($ADObject in $Identity) { # Lets build our diagram #[int] $Level = $($ADObject.Nesting) + 1 $ID = "$($ADObject.DomainName)$($ADObject.DistinguishedName)" #[int] $LevelParent = $($ADObject.Nesting) $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroupDN)" 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 $Script:ConfigurationIcons.ImageUser } 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 = $Script:ConfigurationIcons.ImageGroup } else { $BorderColor = 'Blue' $Image = $Script:ConfigurationIcons.ImageGroupNested } #$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 $Script:ConfigurationIcons.ImageComputer } 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 $Script:ConfigurationIcons.ImageOther } 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 #-HierarchicalDirection FromLeftToRight #-HierarchicalSortMethod directed New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200 #New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($Identity) { # Add it's members to diagram foreach ($ADObject in $Identity) { # Lets build our diagram [int] $Level = $($ADObject.Nesting) + 1 $ID = "$($ADObject.DomainName)$($ADObject.DistinguishedName)$Level" [int] $LevelParent = $($ADObject.Nesting) $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroupDN)$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 $Script:ConfigurationIcons.ImageUser -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 = $Script:ConfigurationIcons.ImageGroup } else { $BorderColor = 'Blue' $Image = $Script:ConfigurationIcons.ImageGroupNested } # $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 $Script:ConfigurationIcons.ImageComputer -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 $Script:ConfigurationIcons.ImageOther -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)' { #if ($DataTableID) { # New-DiagramEvent -ID $DataTableID -ColumnID $ColumnID #} #New-DiagramOptionsLayout -HierarchicalEnabled $true -HierarchicalDirection FromLeftToRight #-HierarchicalSortMethod directed #New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 50 New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($ADGroup) { # Add it's members to diagram foreach ($ADObject in $ADGroup) { # Lets build our diagram # This diagram of Summary doesn't use level checking because it's a summary of a groups, and the level will be different per group # This means that it will look a bit different than what is there when comparing 1 to 1 with the other diagrams #[int] $Level = $($ADObject.Nesting) + 1 $ID = "$($ADObject.DomainName)$($ADObject.DistinguishedName)" #[int] $LevelParent = $($ADObject.Nesting) $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroupDN)" # We track connection for ID to make sure that only once the conenction is added 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 $Script:ConfigurationIcons.ImageUser } 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 = $Script:ConfigurationIcons.ImageGroup } else { $BorderColor = 'Blue' $Image = $Script:ConfigurationIcons.ImageGroupNested } #$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 $Script:ConfigurationIcons.ImageComputer } 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 $Script:ConfigurationIcons.ImageOther } 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 #-HierarchicalDirection FromLeftToRight #-HierarchicalSortMethod directed New-DiagramOptionsPhysics -Enabled $true -HierarchicalRepulsionAvoidOverlap 1 -HierarchicalRepulsionNodeDistance 200 #New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion if ($ADGroup) { # Add it's members to diagram foreach ($ADObject in $ADGroup) { # This diagram of Summary doesn't use level checking because it's a summary of a groups, and the level will be different per group # This means that it will look a bit different than what is there when comparing 1 to 1 with the other diagrams # Lets build our diagram #[int] $Level = $($ADObject.Nesting) + 1 $ID = "$($ADObject.DomainName)$($ADObject.DistinguishedName)" #[int] $LevelParent = $($ADObject.Nesting) $IDParent = "$($ADObject.ParentGroupDomain)$($ADObject.ParentGroupDN)" [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 $Script:ConfigurationIcons.ImageUser -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 = $Script:ConfigurationIcons.ImageGroup } else { $BorderColor = 'Blue' $Image = $Script:ConfigurationIcons.ImageGroupNested } #$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 $Script:ConfigurationIcons.ImageComputer -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 $Script:ConfigurationIcons.ImageOther -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-HTMLReportADEssentials { [cmdletBinding()] param( [Array] $Type, [switch] $Online, [switch] $HideHTML, [string] $FilePath ) 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 } function New-HTMLReportADEssentialsWithSplit { [cmdletBinding()] param( [Array] $Type, [switch] $Online, [switch] $HideHTML, [string] $FilePath, [string] $CurrentReport ) # Split reports into multiple files for easier viewing $DateName = $(Get-Date -f yyyy-MM-dd_HHmmss) $FileName = [io.path]::GetFileNameWithoutExtension($FilePath) $DirectoryName = [io.path]::GetDirectoryName($FilePath) foreach ($T in $Script:ADEssentialsConfiguration.Keys) { if ($Script:ADEssentialsConfiguration[$T].Enabled -eq $true -and ((-not $CurrentReport) -or ($CurrentReport -and $CurrentReport -eq $T))) { $NewFileName = $FileName + '_' + $T + "_" + $DateName + '.html' $FilePath = [io.path]::Combine($DirectoryName, $NewFileName) New-HTML -Author 'PrzemysÅ‚aw KÅ‚ys' -TitleText "ADEssentials $CurrentReport 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 ($Script:ADEssentialsConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ADEssentialsConfiguration[$T]['Summary'] } & $Script:ADEssentialsConfiguration[$T]['Solution'] } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath } } } function Remove-PrivateACL { [cmdletBinding(SupportsShouldProcess)] param( [PSCustomObject] $ACL, [string] $Principal, [alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights] $AccessRule, [System.Security.AccessControl.AccessControlType] $AccessControlType, [Alias('ObjectTypeName')][string[]] $IncludeObjectTypeName, [Alias('InheritedObjectTypeName')][string[]] $IncludeInheritedObjectTypeName, [alias('ActiveDirectorySecurityInheritance', 'IncludeActiveDirectorySecurityInheritance')][nullable[System.DirectoryServices.ActiveDirectorySecurityInheritance]] $InheritanceType, [switch] $Force, [alias('ActiveDirectorySecurity')][System.DirectoryServices.ActiveDirectorySecurity] $NTSecurityDescriptor ) $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $ACL.DistinguishedName $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] $OutputRequiresCommit = @( # if access rule is defined with just remove access rule we want to remove if ($ntSecurityDescriptor -and $ACL.PSObject.Properties.Name -notcontains 'ACLAccessRules') { try { # We do last minute filtering here to ensure we don't remove the wrong ACL if ($Principal) { $PrincipalRequested = Convert-Identity -Identity $Principal -Verbose:$false } $SplatFilteredACL = @{ # I am not sure on this $ACL, needs testing ACL = $ACL.Bundle Resolve = $true Principal = $Principal #Inherited = $Inherited #NotInherited = $NotInherited AccessControlType = $AccessControlType IncludeObjectTypeName = $IncludeObjectTypeName IncludeInheritedObjectTypeName = $IncludeInheritedObjectTypeName #ExcludeObjectTypeName = $ExcludeObjectTypeName #ExcludeInheritedObjectTypeName = $ExcludeInheritedObjectTypeName #IncludeActiveDirectoryRights = $IncludeActiveDirectoryRights #ExcludeActiveDirectoryRights = $ExcludeActiveDirectoryRights IncludeActiveDirectorySecurityInheritance = $InheritanceType ExcludeActiveDirectorySecurityInheritance = $ExcludeActiveDirectorySecurityInheritance PrincipalRequested = $PrincipalRequested Bundle = $Bundle } Remove-EmptyValue -Hashtable $SplatFilteredACL $CheckAgainstFilters = Get-FilteredACL @SplatFilteredACL if (-not $CheckAgainstFilters) { continue } # Now we do remove the ACL Write-Verbose -Message "Remove-ADACL - Removing access from $($ACL.CanonicalName) (type: $($ACL.ObjectClass), IsInherited: $($ACL.IsInherited)) for $($ACL.Principal) / $($ACL.ActiveDirectoryRights) / $($ACL.AccessControlType) / $($ACL.ObjectTypeName) / $($ACL.InheritanceType) / $($ACL.InheritedObjectTypeName)" #Write-Verbose -Message "Remove-ADACL - Removing access from $($Rule.CanonicalName) (type: $($Rule.ObjectClass), IsInherited: $($Rule.IsInherited)) for $($Rule.Principal) / $($Rule.ActiveDirectoryRights) / $($Rule.AccessControlType) / $($Rule.ObjectTypeName) / $($Rule.InheritanceType) / $($Rule.InheritedObjectTypeName)" if ($ACL.IsInherited) { if ($Force) { # isProtected - true to protect the access rules associated with this ObjectSecurity object from inheritance; false to allow inheritance. # preserveInheritance - true to preserve inherited access rules; false to remove inherited access rules. This parameter is ignored if isProtected is false. $ntSecurityDescriptor.SetAccessRuleProtection($true, $true) } else { Write-Warning "Remove-ADACL - Rule for $($ACL.Principal) / $($ACL.ActiveDirectoryRights) / $($ACL.AccessControlType) / $($ACL.ObjectTypeName) / $($ACL.InheritanceType) / $($ACL.InheritedObjectTypeName) is inherited. Use -Force to remove it." continue } } $ntSecurityDescriptor.RemoveAccessRuleSpecific($ACL.Bundle) $true } catch { Write-Warning "Remove-ADACL - Removing access from $($ACL.CanonicalName) (type: $($ACL.ObjectClass), IsInherited: $($ACL.IsInherited)) failed: $($_.Exception.Message)" $false } } elseif ($ACL.PSObject.Properties.Name -contains 'ACLAccessRules') { foreach ($Rule in $ACL.ACLAccessRules) { # We do last minute filtering here to ensure we don't remove the wrong ACL if ($Principal) { $PrincipalRequested = Convert-Identity -Identity $Principal -Verbose:$false } $SplatFilteredACL = @{ ACL = $Rule.Bundle Resolve = $true Principal = $Principal #Inherited = $Inherited #NotInherited = $NotInherited AccessControlType = $AccessControlType IncludeObjectTypeName = $IncludeObjectTypeName IncludeInheritedObjectTypeName = $IncludeInheritedObjectTypeName #ExcludeObjectTypeName = $ExcludeObjectTypeName #ExcludeInheritedObjectTypeName = $ExcludeInheritedObjectTypeName #IncludeActiveDirectoryRights = $IncludeActiveDirectoryRights #ExcludeActiveDirectoryRights = $ExcludeActiveDirectoryRights IncludeActiveDirectorySecurityInheritance = $InheritanceType ExcludeActiveDirectorySecurityInheritance = $ExcludeActiveDirectorySecurityInheritance PrincipalRequested = $PrincipalRequested Bundle = $Bundle } Remove-EmptyValue -Hashtable $SplatFilteredACL $CheckAgainstFilters = Get-FilteredACL @SplatFilteredACL if (-not $CheckAgainstFilters) { continue } # Now we do remove the ACL $ntSecurityDescriptor = $ACL.ACL try { Write-Verbose -Message "Remove-ADACL - Removing access from $($Rule.CanonicalName) (type: $($Rule.ObjectClass), IsInherited: $($Rule.IsInherited)) for $($Rule.Principal) / $($Rule.ActiveDirectoryRights) / $($Rule.AccessControlType) / $($Rule.ObjectTypeName) / $($Rule.InheritanceType) / $($Rule.InheritedObjectTypeName)" if ($Rule.IsInherited) { if ($Force) { # isProtected - true to protect the access rules associated with this ObjectSecurity object from inheritance; false to allow inheritance. # preserveInheritance - true to preserve inherited access rules; false to remove inherited access rules. This parameter is ignored if isProtected is false. $ntSecurityDescriptor.SetAccessRuleProtection($true, $true) } else { Write-Warning "Remove-ADACL - Rule for $($Rule.Principal) / $($Rule.ActiveDirectoryRights) / $($Rule.AccessControlType) / $($Rule.ObjectTypeName) / $($Rule.InheritanceType) / $($Rule.InheritedObjectTypeName) is inherited. Use -Force to remove it." continue } } $ntSecurityDescriptor.RemoveAccessRuleSpecific($Rule.Bundle) #Write-Verbose -Message "Remove-ADACL - Removing access for $($Identity) / $AccessControlType / $Rule" $true } catch { Write-Warning "Remove-ADACL - Removing access from $($Rule.CanonicalName) (type: $($Rule.ObjectClass), IsInherited: $($Rule.IsInherited)) failed: $($_.Exception.Message)" $false } } } else { $AllRights = $false $ntSecurityDescriptor = $ACL.ACL # ACL not provided, we need to get all ourselves if ($Principal -like '*-*-*-*') { $Identity = [System.Security.Principal.SecurityIdentifier]::new($Principal) } else { [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal) } if ($ObjectType -and $InheritanceType -and $AccessRule -and $AccessControlType) { $ObjectTypeGuid = Convert-ADSchemaToGuid -SchemaName $ObjectType if ($ObjectTypeGuid) { $AccessRuleToRemove = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType, $ObjectTypeGuid, $InheritanceType) } else { Write-Warning "Remove-PrivateACL - Object type '$ObjectType' not found in schema" return } } elseif ($ObjectType -and $AccessRule -and $AccessControlType) { $ObjectTypeGuid = Convert-ADSchemaToGuid -SchemaName $ObjectType if ($ObjectTypeGuid) { $AccessRuleToRemove = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType, $ObjectTypeGuid) } else { Write-Warning "Remove-PrivateACL - Object type '$ObjectType' not found in schema" return } } elseif ($AccessRule -and $AccessControlType) { $AccessRuleToRemove = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $AccessRule, $AccessControlType) } else { # this is kind of special we fix it later on, it means user requersted Identity, AccessControlType but nothing else # Since there's no direct option with ActiveDirectoryAccessRule we fix it using RemoveAccess $AllRights = $true } try { if ($AllRights) { Write-Verbose "Remove-ADACL - Removing access for $($Identity) / $AccessControlType / All Rights" $ntSecurityDescriptor.RemoveAccess($Identity, $AccessControlType) } else { Write-Verbose "Remove-ADACL - Removing access for $($AccessRuleToRemove.IdentityReference) / $($AccessRuleToRemove.ActiveDirectoryRights) / $($AccessRuleToRemove.AccessControlType) / $($AccessRuleToRemove.ObjectType) / $($AccessRuleToRemove.InheritanceType) to $($ACL.DistinguishedName)" $ntSecurityDescriptor.RemoveAccessRule($AccessRuleToRemove) } $true } catch { Write-Warning "Remove-ADACL - Error removing permissions for $($Identity) / $($AccessControlType) due to error: $($_.Exception.Message)" $false } } ) if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) { Write-Verbose "Remove-ADACL - Saving permissions for $($ACL.DistinguishedName) on $($QueryServer)" try { # TODO: This is a workaround for a ProtectedFromAccidentalDeletion # It seems if there's Everyone involved in ntSecurityDescriptor it sets back Protected from Accidental Deletion # Need to write some detection mechanism around it $TemporaryObject = Get-ADObject -Identity $ACL.DistinguishedName -Properties ProtectedFromAccidentalDeletion -Server $QueryServer Set-ADObject -Identity $ACL.DistinguishedName -Replace @{ ntSecurityDescriptor = $ntSecurityDescriptor } -ErrorAction Stop -Server $QueryServer #-ProtectedFromAccidentalDeletion $true $AfterTemporaryObject = Get-ADObject -Identity $ACL.DistinguishedName -Properties ProtectedFromAccidentalDeletion -Server $QueryServer if ($TemporaryObject.ProtectedFromAccidentalDeletion -ne $AfterTemporaryObject.ProtectedFromAccidentalDeletion) { Write-Warning -Message "Remove-ADACL - Restoring ProtectedFromAccidentalDeletion from $($AfterTemporaryObject.ProtectedFromAccidentalDeletion) to $($TemporaryObject.ProtectedFromAccidentalDeletion) for $($ACL.DistinguishedName) as a workaround on $($QueryServer)" Set-ADObject -Identity $ACL.DistinguishedName -ProtectedFromAccidentalDeletion $TemporaryObject.ProtectedFromAccidentalDeletion -ErrorAction Stop -Server $QueryServer } # Old way of doing things # Set-Acl -Path $ACL.Path -AclObject $ntSecurityDescriptor -ErrorAction Stop } catch { Write-Warning "Remove-ADACL - Saving permissions for $($ACL.DistinguishedName) failed: $($_.Exception.Message) oon $($QueryServer)" } } elseif ($OutputRequiresCommit -contains $false) { Write-Warning "Remove-ADACL - Skipping saving permissions for $($ACL.DistinguishedName) due to errors." } else { Write-Verbose "Remove-ADACL - No changes for $($ACL.DistinguishedName)" } } 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] @{ AccountDelegation = $Script:ShowWinADAccountDelegation Users = $Script:ShowWinADUser Computers = $Script:ShowWinADComputer Groups = $Script:ShowWinADGroup Laps = $Script:ConfigurationLAPS LapsACL = $Script:ConfigurationLAPSACL LapsAndBitLocker = $Script:ConfigurationLAPSAndBitlocker BitLocker = $Script:ConfigurationBitLocker ServiceAccounts = $Script:ConfigurationServiceAccounts ForestACLOwners = $Script:ConfigurationACLOwners PasswordPolicies = $Script:ConfigurationPasswordPolicies } function Test-ADSubnet { [cmdletBinding()] param( [Array] $Subnets ) foreach ($Subnet in $Subnets) { # we only check for IPV4, I have no clue for IPV6 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 ) #$DomainPDC = $ForestInformation['DomainDomainControllers'][$Domain] | Where-Object { $_.IsPDC -eq $true } $DomainInformation = Get-WinADDomain -Domain $Domain $DomainPDC = $DomainInformation.PdcRoleOwner.Name $PropertiesTrustWMI = @( 'FlatName', 'SID', 'TrustAttributes', 'TrustDirection', 'TrustedDCName', 'TrustedDomain', 'TrustIsOk', 'TrustStatus', 'TrustStatusString', # TrustIsOk/TrustStatus are covered by this '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 { '' } #'TrustOK' = if ($null -ne $Status) { $Status.TrustIsOK } else { $false } #'TrustStatusInt' = if ($null -ne $Status) { $Status.TrustStatus } else { -1 } } } } else { [PSCustomObject] @{ 'TrustSource' = $DomainInformation.Name 'TrustPartner' = $TrustedDomain 'TrustAttributes' = 'Error - needs fixing' 'TrustStatus' = 'N/A' 'TrustSourceDC' = '' 'TrustTargetDC' = '' #'TrustOK' = $false #'TrustStatusInt' = -1 } } } 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" } # code based on ChrisDent $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 # Declare a script level variable which can be used to return information from the delegate. New-Variable LdapCertificate -Scope Script -Force # Create a callback delegate to retrieve the negotiated certificate. # Note: # * The certificate is unlikely to return the subject. # * The delegate is documented as using the X509Certificate type, automatically casting this to X509Certificate2 allows access to more information. $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 = @{ # https://docs.microsoft.com/en-us/dotnet/api/system.security.authentication.exchangealgorithmtype?view=netcore-3.1 '0' = 'None' # No key exchange algorithm is used. '43522' = 'DiffieHellman' # The Diffie Hellman ephemeral key exchange algorithm. '41984' = 'RsaKeyX' # The RSA public-key exchange algorithm. '9216' = 'RsaSign' # The RSA public-key signature algorithm. '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 { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER ServerName Parameter description .PARAMETER Port Parameter description .PARAMETER Credential Parameter description .PARAMETER Identity User to search for using LDAP query by objectGUID, objectSID, SamAccountName, UserPrincipalName, Name or DistinguishedName .EXAMPLE Test-LDAPPorts -ServerName 'SomeServer' -port 3269 -Credential (Get-Credential) .EXAMPLE Test-LDAPPorts -ServerName 'SomeServer' -port 3269 .NOTES General notes #> [CmdletBinding()] param( [string] $ServerName, [int] $Port, [pscredential] $Credential, [string] $Identity ) if ($ServerName -and $Port -ne 0) { Write-Verbose "Test-LDAPPorts - Processing $ServerName / $Port" try { $LDAP = "LDAP://" + $ServerName + ':' + $Port if ($Credential) { $Connection = [ADSI]::new($LDAP, $Credential.UserName, $Credential.GetNetworkCredential().Password) } else { $Connection = [ADSI]($LDAP) } $Connection.Close() $ReturnData = [ordered] @{ 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 $ServerName`:$Port. Error: $ErrorMessage" } else { Write-Warning -Message "Test-LDAPPorts - Error: $ErrorMessage" } $ReturnData = [ordered] @{ Computer = $ServerName Port = $Port Status = $false ErrorMessage = $ErrorMessage } } if ($Identity) { if ($ReturnData.Status -eq $true) { try { Write-Verbose "Test-LDAPPorts - Processing $ServerName / $Port / $Identity" $LDAP = "LDAP://" + $ServerName + ':' + $Port if ($Credential) { $Connection = [ADSI]::new($LDAP, $Credential.UserName, $Credential.GetNetworkCredential().Password) } else { $Connection = [ADSI]($LDAP) } $Searcher = [System.DirectoryServices.DirectorySearcher]$Connection $Searcher.Filter = "(|(DistinguishedName=$Identity)(Name=$Identity)(SamAccountName=$Identity)(UserPrincipalName=$Identity)(objectGUID=$Identity)(objectSid=$Identity))" $SearchResult = $Searcher.FindOne() $SearchResult if ($SearchResult) { $UserFound = $true } else { $UserFound = $false } $ReturnData['Identity'] = $Identity $ReturnData['IdentityStatus'] = $UserFound $ReturnData['IdentityData'] = $SearchResult $ReturnData['IdentityErrorMessage'] = "" $Connection.Close() } 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 $ServerName`:$Port. Error: $ErrorMessage" } else { Write-Warning -Message "Test-LDAPPorts - Error: $ErrorMessage" } $ReturnData['Identity'] = $Identity $ReturnData['IdentityStatus'] = $false $ReturnData['IdentityData'] = $null $ReturnData['IdentityErrorMessage'] = $ErrorMessage } } else { $ReturnData['Identity'] = $Identity $ReturnData['IdentityStatus'] = $false $ReturnData['IdentityData'] = $null $ReturnData['IdentityErrorMessage'] = $ReturnData.ErrorMessage } } [PSCustomObject] $ReturnData } } function Test-LdapServer { [cmdletBinding()] param( [string] $ServerName, [string] $Computer, [PSCustomObject] $Advanced, [int] $GCPortLDAP = 3268, [int] $GCPortLDAPSSL = 3269, [int] $PortLDAP = 389, [int] $PortLDAPS = 636, [switch] $VerifyCertificate, [PSCredential] $Credential, [string] $Identity, [switch] $SkipCheckGC ) if ($ServerName -notlike '*.*') { # $FQDN = $false # querying SSL won't work for non-fqdn, we check if after all our checks it's string with dot. $GlobalCatalogSSL = [PSCustomObject] @{ Status = $false; ErrorMessage = 'No FQDN' } $ConnectionLDAPS = [PSCustomObject] @{ Status = $false; ErrorMessage = 'No FQDN' } if ($PSBoundParameters.ContainsKey('Credential')) { if (-not $SkipCheckGC) { if (-not $Advanced) { $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP -Credential $Credential -Identity $Identity } else { if ($Advanced.IsGlobalCatalog) { $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP -Credential $Credential -Identity $Identity } else { $GlobalCatalogNonSSL = [PSCustomObject] @{ Status = $null; ErrorMessage = 'Not Global Catalog' } } } } $ConnectionLDAP = Test-LDAPPorts -ServerName $ServerName -Port $PortLDAP -Credential $Credential -Identity $Identity } else { if (-not $SkipCheckGC) { if (-not $Advanced) { $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP -Identity $Identity } else { if ($Advanced.IsGlobalCatalog) { $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP -Identity $Identity } else { $GlobalCatalogNonSSL = [PSCustomObject] @{ Status = $null; ErrorMessage = 'Not Global Catalog' } } } } $ConnectionLDAP = Test-LDAPPorts -ServerName $ServerName -Port $PortLDAP -Identity $Identity } } else { if ($PSBoundParameters.ContainsKey('Credential')) { if (-not $SkipCheckGC) { if (-not $Advanced) { $GlobalCatalogSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAPSSL -Credential $Credential -Identity $Identity $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP -Credential $Credential -Identity $Identity } else { if ($Advanced.IsGlobalCatalog) { $GlobalCatalogSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAPSSL -Credential $Credential -Identity $Identity $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP -Credential $Credential -Identity $Identity } else { $GlobalCatalogSSL = [PSCustomObject] @{ Status = $null; ErrorMessage = 'Not Global Catalog' } $GlobalCatalogNonSSL = [PSCustomObject] @{ Status = $null; ErrorMessage = 'Not Global Catalog' } } } } $ConnectionLDAPS = Test-LDAPPorts -ServerName $ServerName -Port $PortLDAPS -Credential $Credential -Identity $Identity $ConnectionLDAP = Test-LDAPPorts -ServerName $ServerName -Port $PortLDAP -Credential $Credential -Identity $Identity } else { if (-not $SkipCheckGC) { if (-not $Advanced) { $GlobalCatalogSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAPSSL -Identity $Identity $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP -Identity $Identity } else { if ($Advanced -and $Advanced.IsGlobalCatalog) { $GlobalCatalogSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAPSSL -Identity $Identity $GlobalCatalogNonSSL = Test-LDAPPorts -ServerName $ServerName -Port $GCPortLDAP -Identity $Identity } else { $GlobalCatalogSSL = [PSCustomObject] @{ Status = $null; ErrorMessage = 'Not Global Catalog' } $GlobalCatalogNonSSL = [PSCustomObject] @{ Status = $null; ErrorMessage = 'Not Global Catalog' } } } } $ConnectionLDAPS = Test-LDAPPorts -ServerName $ServerName -Port $PortLDAPS -Identity $Identity $ConnectionLDAP = Test-LDAPPorts -ServerName $ServerName -Port $PortLDAP -Identity $Identity } } $PortsThatWork = @( if ($GlobalCatalogNonSSL.Status) { $GCPortLDAP } if ($GlobalCatalogSSL.Status) { $GCPortLDAPSSL } if ($ConnectionLDAP.Status) { $PortLDAP } if ($ConnectionLDAPS.Status) { $PortLDAPS } ) | Sort-Object $PortsIdentityStatus = @( if ($GlobalCatalogNonSSL.IdentityStatus) { $GCPortLDAP } if ($GlobalCatalogSSL.IdentityStatus) { $GCPortLDAPSSL } if ($ConnectionLDAP.IdentityStatus) { $PortLDAP } if ($ConnectionLDAPS.IdentityStatus) { $PortLDAPS } ) | Sort-Object $ListIdentityStatus = @( $GlobalCatalogSSL.IdentityStatus $GlobalCatalogNonSSL.IdentityStatus $ConnectionLDAP.IdentityStatus $ConnectionLDAPS.IdentityStatus ) if ($ListIdentityStatus -contains $false) { $IsIdentical = $false } else { $IsIdentical = $true } if ($VerifyCertificate) { if ($PSBoundParameters.ContainsKey("Credential")) { $Certificate = Test-LDAPCertificate -Computer $ServerName -Port $PortLDAPS -Credential $Credential if (-not $Advanced) { $CertificateGC = Test-LDAPCertificate -Computer $ServerName -Port $GCPortLDAPSSL -Credential $Credential } else { if ($Advanced.IsGlobalCatalog) { $CertificateGC = Test-LDAPCertificate -Computer $ServerName -Port $GCPortLDAPSSL -Credential $Credential } else { $CertificateGC = [PSCustomObject] @{ Status = 'N/A'; ErrorMessage = 'Not Global Catalog' } } } } else { $Certificate = Test-LDAPCertificate -Computer $ServerName -Port $PortLDAPS if (-not $Advanced) { $CertificateGC = Test-LDAPCertificate -Computer $ServerName -Port $GCPortLDAPSSL } else { if ($Advanced.IsGlobalCatalog) { $CertificateGC = Test-LDAPCertificate -Computer $ServerName -Port $GCPortLDAPSSL } else { $CertificateGC = [PSCustomObject] @{ Status = 'N/A'; ErrorMessage = 'Not Global Catalog' } } } } } 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 ',' Identity = $Identity IdentityStatus = $IsIdentical IdentityAvailablePorts = $PortsIdentityStatus -join ',' IdentityData = $null IdentityErrorMessage = $null IdentityGCLDAP = $GlobalCatalogNonSSL.IdentityStatus IdentityGCLDAPS = $GlobalCatalogSSL.IdentityStatus IdentityLDAP = $ConnectionLDAP.IdentityStatus IdentityLDAPS = $ConnectionLDAPS.IdentityStatus 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 ',' Identity = $Identity IdentityStatus = $IsIdentical IdentityAvailablePorts = $PortsIdentityStatus -join ',' IdentityData = $null IdentityErrorMessage = $null OperatingSystem = $Advanced.OperatingSystem IPV4Address = $Advanced.IPV4Address IPV6Address = $Advanced.IPV6Address } } if ($VerifyCertificate) { $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 ($Identity) { $Output['IdentityData'] = $ConnectionLDAP.IdentityData $Output['IdentityErrorMessage'] = $ConnectionLDAP.IdentityErrorMessage } else { $Output.Remove('Identity') $Output.Remove('IdentityStatus') $Output.Remove('IdentityAvailablePorts') $Output.Remove('IdentityData') $Output.Remove('IdentityErrorMessage') $Output.Remove('IdentityGCLDAP') $Output.Remove('IdentityGCLDAPS') $Output.Remove('IdentityLDAP') $Output.Remove('IdentityLDAPS') } if (-not $Advanced) { $Output.Remove('IPV4Address') $Output.Remove('OperatingSystem') $Output.Remove('IPV6Address') $Output.Remove('Site') $Output.Remove('IsRO') $Output.Remove('IsGC') } # lets return the objects if required if ($Extended) { $Output['GlobalCatalogSSL'] = $GlobalCatalogSSL $Output['GlobalCatalogNonSSL'] = $GlobalCatalogNonSSL $Output['ConnectionLDAP'] = $ConnectionLDAP $Output['ConnectionLDAPS'] = $ConnectionLDAPS $Output['Certificate'] = $Certificate $Output['CertificateGC'] = $CertificateGC } [PSCustomObject] $Output } function Add-ADACL { [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ADObject')] param( [parameter(Mandatory, ParameterSetName = 'ActiveDirectoryAccessRule')] [Parameter(Mandatory, ParameterSetName = 'ADObject')][alias('Identity')][string] $ADObject, [Parameter(Mandatory, ParameterSetName = 'ACL')][Array] $ACL, [Parameter(Mandatory, ParameterSetName = 'ACL')] [Parameter(Mandatory, ParameterSetName = 'ADObject')] [string] $Principal, [Parameter(Mandatory, ParameterSetName = 'ACL')] [Parameter(Mandatory, ParameterSetName = 'ADObject')] [alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights] $AccessRule, [Parameter(Mandatory, ParameterSetName = 'ACL')] [Parameter(Mandatory, ParameterSetName = 'ADObject')] [System.Security.AccessControl.AccessControlType] $AccessControlType, [Parameter(ParameterSetName = 'ACL')] [Parameter(ParameterSetName = 'ADObject')] [alias('ObjectTypeName')][string] $ObjectType, [Parameter(ParameterSetName = 'ACL')] [Parameter(ParameterSetName = 'ADObject')] [alias('InheritedObjectTypeName')][string] $InheritedObjectType, [Parameter(ParameterSetName = 'ACL')] [Parameter(ParameterSetName = 'ADObject')] [alias('ActiveDirectorySecurityInheritance')][nullable[System.DirectoryServices.ActiveDirectorySecurityInheritance]] $InheritanceType, [parameter(ParameterSetName = 'ADObject', Mandatory = $false)] [parameter(ParameterSetName = 'ACL', Mandatory = $false)] [parameter(ParameterSetName = 'ActiveDirectoryAccessRule', Mandatory = $false)] [alias('ActiveDirectorySecurity')][System.DirectoryServices.ActiveDirectorySecurity] $NTSecurityDescriptor, [parameter(ParameterSetName = 'ActiveDirectoryAccessRule', Mandatory = $true)] [System.DirectoryServices.ActiveDirectoryAccessRule] $ActiveDirectoryAccessRule ) if (-not $Script:ForestDetails) { Write-Verbose "Add-ADACL - Gathering Forest Details" $Script:ForestDetails = Get-WinADForestDetails } if ($PSBoundParameters.ContainsKey('ActiveDirectoryAccessRule')) { if (-not $ntSecurityDescriptor) { $ntSecurityDescriptor = Get-PrivateACL -ADObject $ADObject } if (-not $NTSecurityDescriptor) { Write-Warning -Message "Add-ADACL - No NTSecurityDescriptor provided and ADObject not found" return } $addPrivateACLSplat = @{ ActiveDirectoryAccessRule = $ActiveDirectoryAccessRule ADObject = $ADObject ntSecurityDescriptor = $ntSecurityDescriptor WhatIf = $WhatIfPreference } Add-PrivateACL @addPrivateACLSplat } elseif ($PSBoundParameters.ContainsKey('NTSecurityDescriptor')) { $addPrivateACLSplat = @{ ntSecurityDescriptor = $ntSecurityDescriptor ADObject = $ADObject Principal = $Principal WhatIf = $WhatIfPreference AccessRule = $AccessRule AccessControlType = $AccessControlType ObjectType = $ObjectType InheritedObjectType = $InheritedObjectType InheritanceType = if ($InheritanceType) { $InheritanceType } else { $null } } Add-PrivateACL @addPrivateACLSplat } elseif ($PSBoundParameters.ContainsKey('ADObject')) { foreach ($Object in $ADObject) { $MYACL = Get-ADACL -ADObject $Object -Verbose -NotInherited -Bundle $addPrivateACLSplat = @{ ACL = $MYACL ADObject = $Object Principal = $Principal WhatIf = $WhatIfPreference AccessRule = $AccessRule AccessControlType = $AccessControlType ObjectType = $ObjectType InheritedObjectType = $InheritedObjectType InheritanceType = if ($InheritanceType) { $InheritanceType } else { $null } NTSecurityDescriptor = $MYACL.ACL } Add-PrivateACL @addPrivateACLSplat } } elseif ($PSBoundParameters.ContainsKey('ACL')) { foreach ($SubACL in $ACL) { $addPrivateACLSplat = @{ ACL = $SubACL Principal = $Principal WhatIf = $WhatIfPreference AccessRule = $AccessRule AccessControlType = $AccessControlType ObjectType = $ObjectType InheritedObjectType = $InheritedObjectType InheritanceType = if ($InheritanceType) { $InheritanceType } else { $null } NTSecurityDescriptor = $SubACL.ACL } Add-PrivateACL @addPrivateACLSplat } } } function Copy-ADOUSecurity { <# .SYNOPSIS Copy AD security from one OU to another. .DESCRIPTION Copies the security for one OU to another with the ability to use a different target group with source group as reference. .PARAMETER SourceOU The reference OU. .PARAMETER TargetOU Target OU to apply security. .PARAMETER SourceGroup The reference group. .PARAMETER TargetGroup Target group to apply security .PARAMETER Execute Switch to execute - leaving this out will result in a dry run (whatif). .EXAMPLE Copy-ADOUSecurity -SourceOU "OU=Finance,DC=contoso,DC=com" -TargetOU "OU=Sales,DC=contoso,DC=com" -SourceGroup "FinanceAdmins" -TargetGroup "SalesAdmins" #> [CmdletBinding()] param ( [Parameter(Mandatory)][string]$SourceOU, [Parameter(Mandatory)][string]$TargetOU, [Parameter(Mandatory)][string]$SourceGroup, [Parameter(Mandatory)][string]$TargetGroup, [System.Management.Automation.PSCredential]$Credential, [switch]$Execute ) process { [string]$sDomain = (Get-ADDomain).NetBIOSName [string]$sServer = (Get-ADDomainController -Writable -Discover).HostName $sSourceOU = $SourceOU.Trim() $sDestOU = $TargetOU.Trim() $sSourceAccount = $SourceGroup.Trim() $sDestAccount = $TargetGroup.Trim() [ADSI]$oSourceOU = "LDAP://{0}/{1}" -f $sServer, $sSourceOU [ADSI]$oTargetOU = "LDAP://{0}/{1}" -f $sServer, $sDestOU if ($Credential) { $oSourceOU.PSBase.Username = $Credential.Username $oSourceOU.PSBase.Password = $Credential.GetNetworkCredential().Password $oTargetOU.PSBase.Username = $Credential.Username $oTargetOU.PSBase.Password = $Credential.GetNetworkCredential().Password } $oDestAccountNT = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList $sDomain, $sDestAccount $oSourceOU.ObjectSecurity.Access | Where-Object { $_.IdentityReference -like "$sDomain\$sSourceAccount" } | ForEach-Object { $ActiveDirectoryRights = $_.ActiveDirectoryRights $AccessControlType = $_.AccessControlType $InheritanceType = $_.InheritanceType $InheritedObjectType = $_.InheritedObjectType $ObjectType = $_.ObjectType $oAce = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($oDestAccountNT, $ActiveDirectoryRights, $AccessControlType, $ObjectType, $InheritanceType, $InheritedObjectType) $oTargetOU.ObjectSecurity.AddAccessRule($oAce) } $oSourceOU.ObjectSecurity.Access | Where-Object { $_.IdentityReference -like "$sDomain\$sSourceAccount" } $oTargetOU.ObjectSecurity.Access | Where-Object { $_.IdentityReference -like "$sDomain\$sDestAccount" } if ($Execute) { try { $oTargetOU.CommitChanges() Write-Verbose -Message "Permissions commited" } catch { $ErrorMessage = $_.Exception.Message Write-Warning -Message $ErrorMessage } } else { Write-Warning -Message "Use the switch -Execute to commit changes" } } } function Disable-ADACLInheritance { <# .SYNOPSIS Disables inheritance of access control entries (ACEs) from parent objects for one or more Active Directory objects or security principals. .DESCRIPTION The Disable-ADACLInheritance function disables inheritance of ACEs from parent objects for one or more Active Directory objects or security principals. This function can be used to prevent unwanted ACEs from being inherited by child objects. .PARAMETER ADObject Specifies one or more Active Directory objects or security principals to disable inheritance of ACEs from parent objects. This parameter is mandatory when the 'ADObject' parameter set is used. .PARAMETER ACL Specifies one or more access control lists (ACLs) to disable inheritance of ACEs from parent objects. This parameter is mandatory when the 'ACL' parameter set is used. .PARAMETER RemoveInheritedAccessRules Indicates whether to remove inherited ACEs from the object or principal. If this switch is specified, inherited ACEs are removed from the object or principal. If this switch is not specified, inherited ACEs are retained on the object or principal. .EXAMPLE Disable-ADACLInheritance -ADObject 'CN=TestOU,DC=contoso,DC=com' This example disables inheritance of ACEs from the parent object for the 'TestOU' organizational unit in the 'contoso.com' domain. .EXAMPLE Disable-ADACLInheritance -ACL $ACL -RemoveInheritedAccessRules This example disables inheritance of ACEs from parent objects for the ACL specified in the $ACL variable, and removes any inherited ACEs from the object or principal. .NOTES #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ADObject')] param( [parameter(ParameterSetName = 'ADObject', Mandatory)][alias('Identity')][Array] $ADObject, [parameter(ParameterSetName = 'ACL', Mandatory)][Array] $ACL, [switch] $RemoveInheritedAccessRules ) if ($ACL) { Set-ADACLInheritance -Inheritance 'Disabled' -ACL $ACL -RemoveInheritedAccessRules:$RemoveInheritedAccessRules.IsPresent } else { Set-ADACLInheritance -Inheritance 'Disabled' -ADObject $ADObject -RemoveInheritedAccessRules:$RemoveInheritedAccessRules.IsPresent } } function Enable-ADACLInheritance { <# .SYNOPSIS Enables inheritance of access control entries (ACEs) from parent objects for one or more Active Directory objects or security principals. .DESCRIPTION The Enable-ADACLInheritance function enables inheritance of ACEs from parent objects for one or more Active Directory objects or security principals. This function can be used to ensure that child objects inherit ACEs from parent objects. .PARAMETER ADObject Specifies one or more Active Directory objects or security principals to enable inheritance of ACEs from parent objects. .PARAMETER ACL Specifies one or more access control lists (ACLs) to enable inheritance of ACEs from parent objects. .EXAMPLE Enable-ADACLInheritance -ADObject 'CN=TestOU,DC=contoso,DC=com' .NOTES General notes #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ADObject')] param( [parameter(ParameterSetName = 'ADObject', Mandatory)][alias('Identity')][Array] $ADObject, [parameter(ParameterSetName = 'ACL', Mandatory)][Array] $ACL ) if ($ACL) { Set-ADACLInheritance -Inheritance 'Enabled' -ACL $ACL } else { Set-ADACLInheritance -Inheritance 'Enabled' -ADObject $ADObject } } function Export-ADACLObject { [cmdletBinding()] param( [parameter(Mandatory)][alias('Identity')][string] $ADObject, [alias('Principal')][string[]] $IncludePrincipal, [string[]] $ExcludePrincipal, [switch] $Bundle, [switch] $OneLiner ) $ACLOutput = Get-ADACL -ADObject $ADObject -Bundle foreach ($ACL in $ACLOutput.ACLAccessRules) { $ConvertedIdentity = Convert-Identity -Identity $ACL.Principal -Verbose:$false if ($ConvertedIdentity.Error) { Write-Warning -Message "Export-ADACLObject - Converting identity $($ACL.Principal) failed with $($ConvertedIdentity.Error). Be warned." } if ($IncludePrincipal) { if ($ConvertedIdentity.Name -notin $IncludePrincipal) { continue } } if ($ExcludePrincipal) { if ($ConvertedIdentity.Name -in $ExcludePrincipal) { continue } } if ($Bundle) { [PSCustomObject] @{ Principal = $ACL.Principal ActiveDirectoryAccessRule = $ACL.Bundle Action = 'Copy' } } else { New-ADACLObject -Principal $ACL.Principal -AccessControlType $ACL.AccessControlType -ObjectType $ACL.ObjectTypeName -InheritedObjectType $ACL.InheritedObjectTypeName -AccessRule $ACL.ActiveDirectoryRights -InheritanceType $ACL.InheritanceType -OneLiner:$OneLiner.IsPresent } } } function Find-WinADObjectDifference { [CmdletBinding(DefaultParameterSetName = 'Standard')] param( [Parameter(ParameterSetName = 'Standard', Mandatory)] [Array] $Identity, [Parameter(ParameterSetName = 'Standard')] [alias('ForestName')][string] $Forest, [Parameter(ParameterSetName = 'Standard')] [string[]] $ExcludeDomains, [Parameter(ParameterSetName = 'Standard')] [alias('Domain', 'Domains')][string[]] $IncludeDomains, [Parameter(ParameterSetName = 'Standard')] [switch] $GlobalCatalog, [string[]] $Properties, [string[]] $AddProperties # [ValidateSet( # 'Summary', # 'DetailsPerProperty', # 'DetailsPerServer', # 'DetailsSummary' # )][string[]] $Modes ) $ForestInformation = Get-WinADForestDetails -Extended $Output = [ordered] @{ List = [System.Collections.Generic.List[Object]]::new() ListDetails = [System.Collections.Generic.List[Object]]::new() ListDetailsReversed = [System.Collections.Generic.List[Object]]::new() ListSummary = [System.Collections.Generic.List[Object]]::new() } $ExcludeProperties = @( 'MemberOf' 'servicePrincipalName' 'WhenChanged' 'DistinguishedName' 'uSNChanged' 'uSNCreated' ) if (-not $Properties) { # $PropertiesUser = @( # 'AccountExpirationDate' # 'accountExpires' # 'AccountLockoutTime' # 'AccountNotDelegated' # 'adminCount' # 'AllowReversiblePasswordEncryption' # 'CannotChangePassword' # 'City' # 'codePage' # 'Company' # 'Country' # 'countryCode' # 'Department' # 'Description' # 'DisplayName' # 'DistinguishedName' # 'Division' # 'EmailAddress' # 'EmployeeID' # 'EmployeeNumber' # 'Enabled' # 'GivenName' # 'HomeDirectory' # 'HomedirRequired' # 'Initials' # 'instanceType' # 'KerberosEncryptionType' # 'LastLogonDate' # 'mail' # 'mailNickname' # 'Manager' # 'MemberOf' # 'MobilePhone' # 'Name' # 'ObjectClass' # 'Office' # 'OfficePhone' # 'Organization' # 'OtherName' # 'PasswordExpired' # 'PasswordLastSet' # 'PasswordNeverExpires' # 'PasswordNotRequired' # 'POBox' # 'PostalCode' # 'PrimaryGroup' # 'primaryGroupID' # 'PrincipalsAllowedToDelegateToAccount' # 'ProfilePath' # 'protocolSettings' # 'proxyAddresses' # 'pwdLastSet' # 'SamAccountName' # 'sAMAccountType' # 'ScriptPath' # 'sDRightsEffective' # 'ServicePrincipalNames' # 'showInAddressBook' # 'SID' # 'SIDHistory' # 'SmartcardLogonRequired' # 'State' # 'StreetAddress' # 'Surname' # 'Title' # 'TrustedForDelegation' # 'TrustedToAuthForDelegation' # 'UseDESKeyOnly' # 'userAccountControl' # 'UserPrincipalName' # 'uSNChanged' # 'uSNCreated' # 'whenChanged' # 'whenCreated' # ) $Properties = @( 'company' 'department' 'Description' 'info' 'l' for ($i = 1; $i -le 15; $i++) { "extensionAttribute$i" } 'manager' #'memberOf' 'facsimileTelephoneNumber' 'givenName' 'homePhone' 'postalCode' 'pager' 'lastLogonTimestamp' 'UserAccountControl', 'DisplayName', 'mailNickname', 'mail', 'ipPhone' 'whenChanged' 'whenCreated' ) } if ($AddProperties) { $Properties += $AddProperties } $Properties = $Properties | Sort-Object -Unique if ($GlobalCatalog) { [Array] $GCs = foreach ($DC in $ForestInformation.ForestDomainControllers) { if ($DC.IsGlobalCatalog) { $DC } } } else { $DomainFromIdentity = ConvertFrom-DistinguishedName -DistinguishedName $Identity[0] -ToDomainCN [Array] $GCs = foreach ($DC in $ForestInformation.ForestDomainControllers) { if ($DC.Domain -eq $DomainFromIdentity) { $DC } } } $CountObject = 0 $CachedReversedObjects = [ordered] @{} foreach ($I in $Identity) { $PrimaryObject = $null if (-not $I.DistinguishedName) { $DN = $I } else { $DN = $I.DistinguishedName } #if ($Modes -contains 'DetailsSummary') { $ADObjectDetailedDifferences = [ordered] @{ DistinguishedName = $DN } #} #if ($Modes -contains 'Details') { $ADObjectSummary = [ordered] @{ DistinguishedName = $DN DifferentServers = [System.Collections.Generic.List[Object]]::new() DifferentServersCount = 0 DifferentProperties = [System.Collections.Generic.List[Object]]::new() SameServers = [System.Collections.Generic.List[Object]]::new() SameServersCount = 0 SameProperties = [System.Collections.Generic.List[Object]]::new() } #} $CachedReversedObjects[$DN] = [ordered] @{} $ADObjectDetailsPerPropertyReversed = [ordered] @{ DistinguishedName = $DN Property = 'Status' } $CachedReversedObjects[$DN]['Status'] = $ADObjectDetailsPerPropertyReversed foreach ($Property in $Properties) { $ADObjectDetailsPerPropertyReversed = [ordered] @{ DistinguishedName = $DN Property = $Property } $CachedReversedObjects[$DN][$Property] = $ADObjectDetailsPerPropertyReversed } $CountObject++ $Count = 0 foreach ($GC in $GCs) { $Count++ Write-Verbose -Message "Find-WinADObjectDifference - Processing object [Object: $CountObject / $($Identity.Count)][DC: $Count / $($GCs.Count)] $($GC.HostName) for $I" # Query the specific object on each GC if ($I -is [Microsoft.ActiveDirectory.Management.ADUser]) { Try { if ($GlobalCatalog) { $ObjectInfo = Get-ADUser -Identity $DN -Server "$($GC.HostName):3268" -ErrorAction Stop -Properties $Properties } else { $ObjectInfo = Get-ADUser -Identity $DN -Server $GC.HostName -Properties $Properties -ErrorAction Stop } } catch { $ObjectInfo = $null Write-Warning "Find-WinADObjectDifference - Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" $ErrorValue = $_.Exception.Message.Replace([System.Environment]::NewLine, '') } } elseif ($I -is [Microsoft.ActiveDirectory.Management.ADComputer]) { Try { if ($GlobalCatalog) { $ObjectInfo = Get-ADComputer -Identity $DN -Server "$($GC.HostName):3268" -ErrorAction Stop -Properties $Properties } else { $ObjectInfo = Get-ADComputer -Identity $DN -Server $GC.HostName -Properties $Properties -ErrorAction Stop } } catch { $ObjectInfo = $null Write-Warning "Find-WinADObjectDifference - Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" $ErrorValue = $_.Exception.Message.Replace([System.Environment]::NewLine, '') } } else { if ($I -is [string] -or $I.DistinguishedName) { Try { if ($GlobalCatalog) { $ObjectInfo = Get-ADObject -Identity $DN -Server "$($GC.HostName):3268" -ErrorAction Stop -Properties $Properties } else { $ObjectInfo = Get-ADObject -Identity $DN -Server $GC.HostName -Properties $Properties -ErrorAction Stop } } catch { $ObjectInfo = $null Write-Warning "Test-ADObject - Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" $ErrorValue = $_.Exception.Message.Replace([System.Environment]::NewLine, '') } } else { $ObjectInfo = $null Write-Warning "Test-ADObject - Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" $ErrorValue = $_.Exception.Message.Replace([System.Environment]::NewLine, '') } } if ($ObjectInfo) { if (-not $PrimaryObject) { $PrimaryObject = $ObjectInfo } $ADObjectDetailsPerProperty = [ordered] @{ DistinguishedName = $DN Server = $GC.HostName Status = 'Exists' } #$CachedReversedObjects[$DN]['Status']['StatusComparison'] = $true $CachedReversedObjects[$DN]['Status'][$GC.HostName] = 'Exists' foreach ($Property in $Properties) { #$CachedReversedObjects[$DN]['Status']['StatusComparison'] = $true # Comparing WhenChanged is not needed, because it is special and will always be different if ($Property -notin $ExcludeProperties) { $PropertyNameSame = "$Property-Same" $PropertyNameDiff = "$Property-Diff" if (-not $ADObjectDetailedDifferences[$PropertyNameSame]) { $ADObjectDetailedDifferences[$PropertyNameSame] = [System.Collections.Generic.List[Object]]::new() } if (-not $ADObjectDetailedDifferences[$PropertyNameDiff]) { $ADObjectDetailedDifferences[$PropertyNameDiff] = [System.Collections.Generic.List[Object]]::new() } if ($Property -in 'MemberOf', 'servicePrincipalName') { # this requires complicated logic for comparison } elseif ($null -eq $($PrimaryObject.$Property) -and $null -eq ($ObjectInfo.$Property)) { # Both are null, so it's the same $ADObjectDetailedDifferences[$PropertyNameSame].Add($GC.HostName) if ($Property -notin $ADObjectSummary.SameProperties) { $ADObjectSummary.SameProperties.Add($Property) } if ($GC.HostName -notin $ADObjectSummary.SameServers) { $ADObjectSummary.SameServers.Add($GC.HostName) } } elseif ($null -eq $PrimaryObject.$Property) { # PrimaryObject is null, but ObjectInfo is not, so it's different $ADObjectDetailedDifferences[$PropertyNameDiff].Add($GC.HostName) if ($Property -notin $ADObjectSummary.DifferentProperties) { $ADObjectSummary.DifferentProperties.Add($Property) } if ($GC.HostName -notin $ADObjectSummary.DifferentServers) { $ADObjectSummary.DifferentServers.Add($GC.HostName) } # $CachedReversedObjects[$DN]['Status']['StatusComparison'] = $false } elseif ($null -eq $ObjectInfo.$Property) { # ObjectInfo is null, but PrimaryObject is not, so it's different $ADObjectDetailedDifferences[$PropertyNameDiff].Add($GC.HostName) if ($Property -notin $ADObjectSummary.DifferentProperties) { $ADObjectSummary.DifferentProperties.Add($Property) } if ($GC.HostName -notin $ADObjectSummary.DifferentServers) { $ADObjectSummary.DifferentServers.Add($GC.HostName) } # $CachedReversedObjects[$DN]['Status']['StatusComparison'] = $false } else { if ($ObjectInfo.$Property -ne $PrimaryObject.$Property) { # Both are not null, and they are different $ADObjectDetailedDifferences[$PropertyNameDiff].Add($GC.HostName) if ($Property -notin $ADObjectSummary.DifferentProperties) { $ADObjectSummary.DifferentProperties.Add($Property) } if ($GC.HostName -notin $ADObjectSummary.DifferentServers) { $ADObjectSummary.DifferentServers.Add($GC.HostName) } # $CachedReversedObjects[$DN]['Status']['StatusComparison'] = $false } else { # Both are not null, and they are the same $ADObjectDetailedDifferences[$PropertyNameSame].Add($GC.HostName) if ($Property -notin $ADObjectSummary.SameProperties) { $ADObjectSummary.SameProperties.Add($Property) } if ($GC.HostName -notin $ADObjectSummary.SameServers) { $ADObjectSummary.SameServers.Add($GC.HostName) } } } } $ADObjectDetailsPerProperty[$Property] = $ObjectInfo.$Property $CachedReversedObjects[$DN][$Property][$GC.HostName] = $ObjectInfo.$Property } $Output.ListDetails.Add([PSCustomObject] $ADObjectDetailsPerProperty) } else { if (-not $PrimaryObject) { $PrimaryObject = $ObjectInfo } $ADObjectDetailsPerProperty = [ordered] @{ DistinguishedName = $DN Server = $GC.HostName Status = $ErrorValue } $ADObjectSummary.DifferentServers.Add($GC.HostName) $CachedReversedObjects[$DN]['Status'][$GC.HostName] = $ErrorValue #$CachedReversedObjects[$DN]['Status']['StatusComparison'] = $false foreach ($Property in $Properties) { if ($Property -notin $ExcludeProperties) { $ADObjectDetailsPerProperty[$Property] = $null $CachedReversedObjects[$DN][$Property][$GC.HostName] = $ObjectInfo.$Property if ($Property -notin $ADObjectSummary.DifferentProperties) { $ADObjectSummary.DifferentProperties.Add($Property) } #$CachedReversedObjects[$DN][$Property]['StatusComparison'] = $false } } $Output.ListDetails.Add([PSCustomObject] $ADObjectDetailsPerProperty) } } $ADObjectSummary.DifferentServersCount = $ADObjectSummary.DifferentServers.Count $ADObjectSummary.SameServersCount = $ADObjectSummary.SameServers.Count $Output.List.Add([PSCustomObject] $ADObjectDetailedDifferences) $Output.ListSummary.Add([PSCustomObject] $ADObjectSummary) foreach ($Object in $CachedReversedObjects[$DN].Keys) { $Output.ListDetailsReversed.Add([PSCustomObject] $CachedReversedObjects[$DN][$Object]) } } $Output } function Get-ADACL { [cmdletbinding()] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [alias('Identity')][Array] $ADObject, [switch] $Extended, [alias('ResolveTypes')][switch] $Resolve, [string] $Principal, [switch] $Inherited, [switch] $NotInherited, [switch] $Bundle, [System.Security.AccessControl.AccessControlType] $AccessControlType, [Alias('ObjectTypeName')][string[]] $IncludeObjectTypeName, [Alias('InheritedObjectTypeName')][string[]] $IncludeInheritedObjectTypeName, [string[]] $ExcludeObjectTypeName, [string[]] $ExcludeInheritedObjectTypeName, [Alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRights, [System.DirectoryServices.ActiveDirectoryRights[]] $ExcludeActiveDirectoryRights, [Alias('InheritanceType', 'IncludeInheritanceType')][System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $IncludeActiveDirectorySecurityInheritance, [Alias('ExcludeInheritanceType')][System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $ExcludeActiveDirectorySecurityInheritance, [switch] $ADRightsAsArray ) Begin { if (-not $Script:ForestGUIDs) { Write-Verbose "Get-ADACL - Gathering Forest GUIDS" $Script:ForestGUIDs = Get-WinADForestGUIDs } if (-not $Script:ForestDetails) { Write-Verbose "Get-ADACL - Gathering Forest Details" $Script:ForestDetails = Get-WinADForestDetails } if ($Principal -and $Resolve) { $PrincipalRequested = Convert-Identity -Identity $Principal -Verbose:$false } } Process { foreach ($Object in $ADObject) { $ADObjectData = $null if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) { # if object already has proper security descriptor we don't need to do additional querying if ($Object.ntSecurityDescriptor) { $ADObjectData = $Object } [string] $DistinguishedName = $Object.DistinguishedName [string] $CanonicalName = $Object.CanonicalName if ($CanonicalName) { $CanonicalName = $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 } if (-not $ADObjectData) { $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $DistinguishedName $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] try { $ADObjectData = Get-ADObject -Identity $DistinguishedName -Properties ntSecurityDescriptor, CanonicalName -ErrorAction Stop -Server $QueryServer # Since we already request an object we might as well use the data and overwrite it if people use the string $ObjectClass = $ADObjectData.ObjectClass $CanonicalName = $ADObjectData.CanonicalName # Real ACL $ACLs = $ADObjectData.ntSecurityDescriptor } catch { Write-Warning "Get-ADACL - Path $PathACL - Error: $($_.Exception.Message)" continue } } else { # Real ACL $ACLs = $ADObjectData.ntSecurityDescriptor } $AccessObjects = foreach ($ACL in $ACLs.Access) { $SplatFilteredACL = @{ ACL = $ACL Resolve = $Resolve Principal = $Principal Inherited = $Inherited NotInherited = $NotInherited AccessControlType = $AccessControlType IncludeObjectTypeName = $IncludeObjectTypeName IncludeInheritedObjectTypeName = $IncludeInheritedObjectTypeName ExcludeObjectTypeName = $ExcludeObjectTypeName ExcludeInheritedObjectTypeName = $ExcludeInheritedObjectTypeName IncludeActiveDirectoryRights = $IncludeActiveDirectoryRights ExcludeActiveDirectoryRights = $ExcludeActiveDirectoryRights IncludeActiveDirectorySecurityInheritance = $IncludeActiveDirectorySecurityInheritance ExcludeActiveDirectorySecurityInheritance = $ExcludeActiveDirectorySecurityInheritance PrincipalRequested = $PrincipalRequested Bundle = $Bundle } Remove-EmptyValue -Hashtable $SplatFilteredACL Get-FilteredACL @SplatFilteredACL } if ($Bundle) { if ($Object.CanonicalName) { $CanonicalName = $Object.CanonicalName } else { $CanonicalName = ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToCanonicalName } [PSCustomObject] @{ DistinguishedName = $DistinguishedName CanonicalName = $CanonicalName ACL = $ACLs ACLAccessRules = $AccessObjects Path = $PathACL } } else { $AccessObjects } } } End { } } function Get-ADACLOwner { <# .SYNOPSIS Gets owner from given Active Directory object .DESCRIPTION Gets owner from given Active Directory object .PARAMETER ADObject Active Directory object to get owner from .PARAMETER Resolve Resolves owner to provide more details about said owner .PARAMETER IncludeACL Include additional ACL information along with owner .PARAMETER IncludeOwnerType Include only specific Owner Type, by default all Owner Types are included .PARAMETER ExcludeOwnerType Exclude specific Owner Type, by default all Owner Types are included .EXAMPLE Get-ADACLOwner -ADObject 'CN=Policies,CN=System,DC=ad,DC=evotec,DC=xyz' -Resolve | Format-Table .NOTES General notes #> [cmdletBinding()] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [alias('Identity')][Array] $ADObject, [switch] $Resolve, [alias('AddACL')][switch] $IncludeACL, [validateSet('WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown')][string[]] $IncludeOwnerType, [validateSet('WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown')][string[]] $ExcludeOwnerType #, # [System.Collections.IDictionary] $ADAdministrativeGroups, # [alias('ForestName')][string] $Forest, # [string[]] $ExcludeDomains, # [alias('Domain', 'Domains')][string[]] $IncludeDomains, # [System.Collections.IDictionary] $ExtendedForestInformation ) Begin { #if (-not $Script:ADAdministrativeGroups -and $Resolve) { #Write-Verbose "Get-GPOZaurrOwner - Getting ADAdministrativeGroups" #$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 #} if (-not $Script:ForestDetails) { Write-Verbose "Get-ADACL - Gathering Forest Details" $Script:ForestDetails = Get-WinADForestDetails } } Process { foreach ($Object in $ADObject) { $ADObjectData = $null if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) { # if object already has proper security descriptor we don't need to do additional querying if ($Object.ntSecurityDescriptor) { $ADObjectData = $Object } [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 # -ObjectDN $DistinguishedName 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 if (-not $ADObjectData) { $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $DistinguishedName $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] try { $ADObjectData = Get-ADObject -Identity $DistinguishedName -Properties ntSecurityDescriptor, CanonicalName, ObjectClass -ErrorAction Stop -Server $QueryServer # Since we already request an object we might as well use the data and overwrite it if people use the string $ObjectClass = $ADObjectData.ObjectClass $CanonicalName = $ADObjectData.CanonicalName # Real ACL $ACLs = $ADObjectData.ntSecurityDescriptor } catch { Write-Warning "Get-ADACL - Path $PathACL - Error: $($_.Exception.Message)" continue } } else { # Real ACL $ACLs = $ADObjectData.ntSecurityDescriptor } $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 ($IncludeACL) { $Hash['ACLs'] = $ACLs } if ($Resolve) { #$Identity = ConvertTo-Identity -Identity $Hash.Owner -ExtendedForestInformation $ForestInformation -ADAdministrativeGroups $ADAdministrativeGroups 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'] = '' } if ($PSBoundParameters.ContainsKey('IncludeOwnerType')) { if ($Hash['OwnerType'] -in $IncludeOwnerType) { } else { continue } } if ($PSBoundParameters.ContainsKey('ExcludeOwnerType')) { if ($Hash['OwnerType'] -in $ExcludeOwnerType) { continue } } } $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 #LDAPFilter = '(objectClass=site)' Filter = "*" 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' -FilterOut -Owner:$Owner } if ($ContainerType -contains 'subnet') { $getADObjectSplat = @{ Server = $QueryServer #LDAPFilter = '(objectClass=subnet)' Filter = "*" 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 ($ContainerType -contains 'interSiteTransport') { $getADObjectSplat = @{ Server = $QueryServer #LDAPFilter = '(objectClass=interSiteTransport)' Filter = '*' 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 ($ContainerType -contains 'siteLink') { $getADObjectSplat = @{ Server = $QueryServer Filter = '*' #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 ($ContainerType -contains 'service') { $getADObjectSplat = @{ Server = $QueryServer #LDAPFilter = '(objectClass=foreignSecurityPrincipal)' Filter = '*' SearchBase = "CN=Services,CN=Configuration,$($($ForestDN))" #SearchScope = 'OneLevel' 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 #LDAPFilter = '(objectClass=foreignSecurityPrincipal)' Filter = '*' 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 } } } 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 IncludeOwnerType Include only specific Owner Type, by default all Owner Types are included .PARAMETER ExcludeOwnerType Exclude specific Owner Type, by default all Owner Types are included .PARAMETER Separate Returns OrderedDictionary with each top level container being in separate key .PARAMETER OutputFile Saves output to Excel file. Requires PSWriteExcel module. This was added to speed up processing and reduce memory usage. When using this option, you can use PassThru option, to get objects as well. .PARAMETER PassThru Returns objects as well as saves to Excel file. Requires PSWriteExcel module. .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, [string[]] $SearchBase, [switch] $Owner, [switch] $Separate, [switch] $IncludeInherited, [validateSet('WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown')][string[]] $IncludeOwnerType, [validateSet('WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown')][string[]] $ExcludeOwnerType, [string] $OutputFile, [switch] $PassThru ) if ($OutputFile) { $CommandExists = Get-Command -Name 'ConvertTo-Excel' -ErrorAction SilentlyContinue if (-not $CommandExists) { Write-Warning -Message "ConvertTo-Excel command is missing. Please install PSWriteExcel module when using OutputFile option." Write-Warning -Message "Install-Module -Name PSWriteExcel -Force -Verbose" return } } $ForestTime = Start-TimeLog $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended $Output = [ordered]@{} foreach ($Domain in $ForestInformation.Domains) { if ($SearchBase) { # Lets do quick removal when domain doesn't match so we don't use search base by accident $Found = $false foreach ($S in $SearchBase) { $DN = $ForestInformation['DomainsExtended'][$Domain].DistinguishedName $CurrentObjectDC = ConvertFrom-DistinguishedName -DistinguishedName $S -ToDC if ($CurrentObjectDC -eq $DN) { $Found = $true break } } if ($Found -eq $false) { continue } } Write-Verbose -Message "Get-WinADACLForest - [Start][Domain $Domain]" $DomainTime = Start-TimeLog $Output[$Domain] = [ordered] @{} $Server = $ForestInformation.QueryServers[$Domain].HostName[0] $DomainStructure = @( if ($SearchBase) { foreach ($S in $SearchBase) { Get-ADObject -Filter "*" -Properties canonicalName, ntSecurityDescriptor -SearchScope Base -SearchBase $S -Server $Server } } else { Get-ADObject -Filter "*" -Properties canonicalName, ntSecurityDescriptor -SearchScope Base -Server $Server Get-ADObject -Filter "*" -Properties canonicalName, ntSecurityDescriptor -SearchScope OneLevel -Server $Server } ) $LdapFilter = "(|(ObjectClass=user)(ObjectClass=contact)(ObjectClass=computer)(ObjectClass=group)(objectClass=inetOrgPerson)(objectClass=foreignSecurityPrincipal)(objectClass=container)(objectClass=organizationalUnit)(objectclass=msDS-ManagedServiceAccount)(objectclass=msDS-GroupManagedServiceAccount))" $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() $ObjectOutputName = "$($Structure.Name)".Replace(' ', '').ToLower() Write-Verbose -Message "Get-WinADACLForest - [Start]$ObjectName" if ($Structure.ObjectClass -eq 'organizationalUnit') { #$Containers = Get-ADOrganizationalUnit -Filter '*' -Server $Server -SearchBase $Structure.DistinguishedName -Properties canonicalName $Ignore = @() $Containers = @( Get-ADObject -LDAPFilter $LdapFilter -SearchBase $Structure.DistinguishedName -Properties canonicalName, ntSecurityDescriptor -Server $Server -SearchScope Subtree | ForEach-Object { $Found = $false foreach ($I in $Ignore) { if ($_.DistinguishedName -like $I) { $Found = $true } } if (-not $Found) { $_ } } ) | Sort-Object canonicalName } elseif ($Structure.ObjectClass -eq 'domainDNS') { $Containers = $Structure } elseif ($Structure.ObjectClass -eq 'container') { $Ignore = @( # lets ignore GPO, we deal with it in GPOZaurr -join ('*CN=Policies,CN=System,', $ForestInformation['DomainsExtended'][$DOmain].DistinguishedName) -join ('*,CN=System,', $ForestInformation['DomainsExtended'][$DOmain].DistinguishedName) ) $Containers = Get-ADObject -LDAPFilter $LdapFilter -SearchBase $Structure.DistinguishedName -Properties canonicalName, ntSecurityDescriptor -Server $Server -SearchScope Subtree | ForEach-Object { $Found = $false foreach ($I in $Ignore) { if ($_.DistinguishedName -like $I) { $Found = $true } } if (-not $Found) { $_ } } | Sort-Object canonicalName } else { $EndTime = Stop-TimeLog -Time $Time -Option OneLiner Write-Verbose -Message "Get-WinADACLForest - [Skip ]$ObjectName[ObjectClass not requested]" continue } if (-not $Containers) { $EndTime = Stop-TimeLog -Time $Time -Option OneLiner Write-Verbose -Message "Get-WinADACLForest - [End ]$ObjectName[$EndTime]" continue } Write-Verbose -Message "Get-WinADACLForest - [Read ]$ObjectName[Objects to process: $($Containers.Count)]" if ($Owner) { $getADACLOwnerSplat = @{ ADObject = $Containers Resolve = $true ExcludeOwnerType = $ExcludeOwnerType IncludeOwnerType = $IncludeOwnerType } Remove-EmptyValue -IDictionary $getADACLOwnerSplat $MYACL = Get-ADACLOwner @getADACLOwnerSplat } else { if ($IncludeInherited) { $MYACL = Get-ADACL -ADObject $Containers -ResolveTypes } else { $MYACL = Get-ADACL -ADObject $Containers -ResolveTypes -NotInherited } } if ($OutputFile) { $TimeExport = Start-TimeLog $Extension = [io.path]::GetExtension($OutputFile) $DirectoryPath = [io.path]::GetDirectoryName($OutputFile) $FileName = [io.path]::GetFileNameWithoutExtension($OutputFile) if ($ForestInformation.Domains.Count -gt 1) { $FinalPath = [io.path]::Combine($DirectoryPath, "$FileName-$Domain$Extension") } else { $FinalPath = [io.path]::Combine($DirectoryPath, "$FileName$Extension") } Write-Verbose -Message "Get-WinADACLForest - [Save ]$ObjectName[OutputFile: $FinalPath]" if ($Structure.ObjectClass -eq 'domainDns') { $WorkSheetName = "$($Structure.CanonicalName)".Replace("/", "") } else { $WorkSheetName = "$($Structure.Name)" } $MYACL | ConvertTo-Excel -FilePath $FinalPath -ExcelWorkSheetName $WorkSheetName -AutoFilter -AutoFit -FreezeTopRowFirstColumn $EndTimeExport = Stop-TimeLog -Time $TimeExport -Option OneLiner Write-Verbose -Message "Get-WinADACLForest - [End ]$ObjectName[OutputFile: $FinalPath][$EndTimeExport]" Write-Verbose -Message "Get-WinADACLForest - [Start]$ObjectName[Garbage Collection]" [System.GC]::Collect() Start-Sleep -Seconds 5 [System.GC]::Collect() Write-Verbose -Message "Get-WinADACLForest - [End ]$ObjectName[Garbage Collection][Done]" if ($PassThru) { $MYACL } } elseif ($Separate) { $Output[$Domain][$ObjectOutputName] = $MYACL } else { $MYACL } $EndTime = Stop-TimeLog -Time $Time -Option OneLiner Write-Verbose -Message "Get-WinADACLForest - [End ]$ObjectName[$EndTime]" } $DomainEndTime = Stop-TimeLog -Time $DomainTime -Option OneLiner Write-Verbose -Message "Get-WinADACLForest - [End ][Domain $Domain][$DomainEndTime]" } $ForestEndTime = Stop-TimeLog -Time $ForestTime -Option OneLiner Write-Verbose -Message "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 $Properties = @( 'Name' 'OperatingSystem' 'OperatingSystemVersion' 'DistinguishedName' 'LastLogonDate' 'PasswordLastSet' 'PrimaryGroupID' if ($ComputerProperties.Name -contains 'ms-Mcs-AdmPwd') { $LapsAvailable = $true 'ms-Mcs-AdmPwd' 'ms-Mcs-AdmPwdExpirationTime' } else { $LapsAvailable = $false } if ($ComputerProperties.Name -contains 'msLAPS-Password') { $WindowsLapsAvailable = $true 'msLAPS-PasswordExpirationTime' 'msLAPS-Password' 'msLAPS-EncryptedPassword' 'msLAPS-EncryptedPasswordHistory' 'msLAPS-EncryptedDSRMPassword' 'msLAPS-EncryptedDSRMPasswordHistory' } else { $WindowsLapsAvailable = $false } ) $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) { # If SearchBase is defined we need to check it belongs to current domain # if it does, great. If not we need to skip it $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 = $null } } if ($WindowsLapsAvailable) { if ($_.'msLAPS-PasswordExpirationTime') { $WindowsLaps = $true $WindowsLapsExpirationDays = Convert-TimeToDays -StartTime ($CurrentDate) -EndTime (Convert-ToDateTime -Timestring ($_.'msLAPS-PasswordExpirationTime')) $WindowsLapsExpirationTime = Convert-ToDateTime -Timestring ($_.'msLAPS-PasswordExpirationTime') $WindowsLapsHistoryCount = $_.'msLAPS-EncryptedPasswordHistory'.Count } else { $WindowsLaps = $false $WindowsLapsExpirationDays = $null $WindowsLapsExpirationTime = $null $WindowsLapsHistoryCount = 0 } } else { $WindowsLaps = $null $WindowsLapsExpirationDays = $null $WindowsLapsExpirationTime = $null $WindowsLapsHistoryCount = 0 } 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 WindowsLaps = $WindowsLaps WindowsLapsExpirationDays = $WindowsLapsExpirationDays WindowsLapsExpirationTime = $WindowsLapsExpirationTime WindowsLapsHistoryCount = $WindowsLapsHistoryCount 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 ($_.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 ($_.PrimaryGroupID -in 516, 521) { $true } else { $false } Encrypted = $Encrypted EncryptedTime = $EncryptedTime Laps = $Laps LapsExpirationDays = $LapsExpirationDays LapsExpirationTime = $LapsExpirationTime WindowsLaps = $WindowsLaps WindowsLapsExpirationDays = $WindowsLapsExpirationDays WindowsLapsExpirationTime = $WindowsLapsExpirationTime WindowsLapsHistoryCount = $WindowsLapsHistoryCount 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 $WindowsLAPS = $false $WindowsLAPSExpirationTime = $false $WindowsLAPSEncryptedPassword = $false #$WindowsLAPSEncryptedPasswordHistory = $false #$WindowsLAPSEncryptedDSRMPassword = $false # $WindowsLAPSEncryptedDSRMPasswordHistory = $false <# msLAPS-PasswordExpirationTime msLAPS-Password msLAPS-EncryptedPassword msLAPS-EncryptedPasswordHistory msLAPS-EncryptedDSRMPassword msLAPS-EncryptedDSRMPasswordHistory #> foreach ($ACL in $ACLs) { if ($ACL.ObjectTypeName -eq 'ms-Mcs-AdmPwd') { # LAPS if ($ACL.AccessControlType -eq 'Allow' -and $ACL.ActiveDirectoryRights -like '*WriteProperty*') { $LAPS = $true } } elseif ($ACL.ObjectTypeName -eq 'ms-Mcs-AdmPwdExpirationTime') { # LAPS if ($ACL.AccessControlType -eq 'Allow' -and $ACL.ActiveDirectoryRights -like '*WriteProperty*') { $LAPSExpirationTime = $true } } elseif ($ACL.ObjectTypeName -eq 'ms-LAPS-Password') { # Windows LAPS if ($ACL.AccessControlType -eq 'Allow' -and $ACL.ActiveDirectoryRights -like '*WriteProperty*') { $WindowsLAPS = $true } } elseif ($ACL.ObjectTypeName -eq 'ms-LAPS-PasswordExpirationTime') { # Windows LAPS if ($ACL.AccessControlType -eq 'Allow' -and $ACL.ActiveDirectoryRights -like '*WriteProperty*') { $WindowsLAPSExpirationTime = $true } } elseif ($ACL.ObjectTypeName -eq 'ms-LAPS-Encrypted-Password-Attributes') { if ($ACL.AccessControlType -eq 'Allow' -and $ACL.ActiveDirectoryRights -like '*WriteProperty*') { $WindowsLAPSEncryptedPassword = $true } } # elseif ($ACL.ObjectTypeName -eq 'ms-LAPS-EncryptedPasswordHistory') { # if ($ACL.AccessControlType -eq 'Allow' -and $ACL.ActiveDirectoryRights -like '*WriteProperty*') { # $WindowsLAPSEncryptedPasswordHistory = $true # } # } elseif ($ACL.ObjectTypeName -eq 'ms-LAPS-EncryptedDSRMPassword') { # if ($ACL.AccessControlType -eq 'Allow' -and $ACL.ActiveDirectoryRights -like '*WriteProperty*') { # $WindowsLAPSEncryptedDSRMPassword = $true # } # } elseif ($ACL.ObjectTypeName -eq 'ms-LAPS-EncryptedDSRMPasswordHistory') { # if ($ACL.AccessControlType -eq 'Allow' -and $ACL.ActiveDirectoryRights -like '*WriteProperty*') { # $WindowsLAPSEncryptedDSRMPasswordHistory = $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 WindowsLAPSACL = $WindowsLAPS WindowsLAPSExpirationACL = $WindowsLAPSExpirationTime WindowsLAPSEncryptedPassword = $WindowsLAPSEncryptedPassword #WindowsLAPSEncryptedPasswordHistory = $WindowsLAPSEncryptedPasswordHistory #WindowsLAPSEncryptedDSRMPassword = $WindowsLAPSEncryptedDSRMPassword #WindowsLAPSEncryptedDSRMPasswordHistory = $WindowsLAPSEncryptedDSRMPasswordHistory 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, [switch] $AddOwner ) $AllUsers = [ordered] @{} $AllContacts = [ordered] @{} $AllGroups = [ordered] @{} $AllComputers = [ordered] @{} $CacheUsersReport = [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 "*" -Properties $Properties -Server $QueryServer #$ForestInformation['QueryServers'][$Domain].HostName[0] $AllContacts[$Domain] = Get-ADObject -Filter 'objectClass -eq "contact"' -Properties SamAccountName, Mail, Name, DistinguishedName, WhenChanged, Whencreated, DisplayName -Server $QueryServer $Properties = @( 'SamAccountName', 'CanonicalName', 'Mail', 'Name', 'DistinguishedName', 'isCriticalSystemObject', 'ObjectSID' ) $AllGroups[$Domain] = Get-ADGroup -Filter "*" -Properties $Properties -Server $QueryServer $Properties = @( 'DistinguishedName', 'LastLogonDate', 'PasswordLastSet', 'Enabled', 'DnsHostName', 'PasswordNeverExpires', 'PasswordNotRequired', 'PasswordExpired', 'ManagedBy', 'OperatingSystemVersion', 'OperatingSystem' , 'TrustedForDelegation', 'WhenCreated', 'WhenChanged', 'PrimaryGroupID' 'nTSecurityDescriptor' ) $AllComputers[$Domain] = Get-ADComputer -Filter "*" -Server $QueryServer -Properties $Properties } foreach ($Domain in $AllUsers.Keys) { foreach ($U in $AllUsers[$Domain]) { $CacheUsersReport[$U.DistinguishedName] = $U } } foreach ($Domain in $AllContacts.Keys) { foreach ($C in $AllContacts[$Domain]) { $CacheUsersReport[$C.DistinguishedName] = $C } } foreach ($Domain in $AllGroups.Keys) { foreach ($G in $AllGroups[$Domain]) { $CacheUsersReport[$G.DistinguishedName] = $G } } $Output = [ordered] @{} foreach ($Domain in $ForestInformation.Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $Output[$Domain] = foreach ($Computer in $AllComputers[$Domain]) { $ComputerLocation = ($Computer.DistinguishedName -split ',').Replace('OU=', '').Replace('CN=', '').Replace('DC=', '') $Region = $ComputerLocation[-4] $Country = $ComputerLocation[-5] if ($Computer.ManagedBy) { $Manager = $CacheUsersReport[$Computer.ManagedBy].Name $ManagerSamAccountName = $CacheUsersReport[$Computer.ManagedBy].SamAccountName $ManagerEmail = $CacheUsersReport[$Computer.ManagedBy].Mail $ManagerEnabled = $CacheUsersReport[$Computer.ManagedBy].Enabled $ManagerLastLogon = $CacheUsersReport[$Computer.ManagedBy].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 { $ManagerStatus = 'Not available' $Manager = $null $ManagerSamAccountName = $null $ManagerEmail = $null $ManagerEnabled = $null $ManagerLastLogon = $null $ManagerLastLogonDays = $null } 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 } if ($AddOwner) { $Owner = Get-ADACLOwner -ADObject $Computer -Verbose -Resolve [PSCustomObject] @{ Name = $Computer.Name SamAccountName = $Computer.SamAccountName Domain = $Domain 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 #OperatingSystemVersion = $Computer.OperatingSystemVersion 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 ManagerStatus = $ManagerStatus Manager = $Manager ManagerSamAccountName = $ManagerSamAccountName ManagerEmail = $ManagerEmail ManagerLastLogonDays = $ManagerLastLogonDays OwnerName = $Owner.OwnerName OwnerSID = $Owner.OwnerSID OwnerType = $Owner.OwnerType ManagerDN = $Computer.ManagedBy Description = $Computer.Description TrustedForDelegation = $Computer.TrustedForDelegation } } else { $Owner = $null [PSCustomObject] @{ Name = $Computer.Name SamAccountName = $Computer.SamAccountName Domain = $Domain 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 #OperatingSystemVersion = $Computer.OperatingSystemVersion 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 ManagerStatus = $ManagerStatus Manager = $Manager ManagerSamAccountName = $ManagerSamAccountName ManagerEmail = $ManagerEmail ManagerLastLogonDays = $ManagerLastLogonDays ManagerDN = $Computer.ManagedBy Description = $Computer.Description TrustedForDelegation = $Computer.TrustedForDelegation } } } } if ($PerDomain) { $Output } else { foreach ($O in $Output.Keys) { $Output[$O] } } } 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 } # This is for case when Get-ADDomainController -Filter "*" is broken $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 { #[Array]$GPOs = @(Get-GPO -All -Domain $Domain -Server $QueryServer) $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.IsPDC 'GroupPolicyOutput' = $null -ne $GPOs # This shows whether output was on Get-GPO "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') } <# PS C:\Windows\system32> Get-CimData -NameSpace "root\microsoftdfs" -Class 'dfsrreplicatedfolderinfo' -ComputerName ad | Where-Object { $_.ReplicationGroupname -eq 'Domain System Volume' } CurrentConflictSizeInMb : 0 CurrentStageSizeInMb : 1 LastConflictCleanupTime : 2020-03-22 23:54:17 LastErrorCode : 0 LastErrorMessageId : 0 LastTombstoneCleanupTime : 2020-03-22 23:54:17 MemberGuid : 9650D20E-0D00-43AC-AC1F-4D11EDC17E27 MemberName : AD ReplicatedFolderGuid : 5FFB282C-A802-4700-89A5-B59B7A0EF671 ReplicatedFolderName : SYSVOL Share ReplicationGroupGuid : C2E87E8F-18CC-41A4-8072-A1B9A4F2ACF6 ReplicationGroupName : Domain System Volume State : 4 PSComputerName : AD #> <# NameSpace "root\microsoftdfs" Class 'dfsrreplicatedfolderinfo' CurrentConflictSizeInMb : 0 CurrentStageSizeInMb : 0 LastConflictCleanupTime : 13.09.2019 07:59:38 LastErrorCode : 0 LastErrorMessageId : 0 LastTombstoneCleanupTime : 13.09.2019 07:59:38 MemberGuid : A8930B63-1405-4E0B-AE43-840DAAC64DCE MemberName : AD1 ReplicatedFolderGuid : 58836C0B-1AB9-49A9-BE64-57689A5A6350 ReplicatedFolderName : SYSVOL Share ReplicationGroupGuid : 7DA3CD45-CF61-4D95-AB46-6DC859DD689B ReplicationGroupName : Domain System Volume State : 2 PSComputerName : AD1 #> $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' #$DomainSummary['ReplicationState'] = $WarningVar -join ', ' } 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 { #$ErrorMessage = $_.Exception.Message $Registry = $null } if ($null -ne $Registry.StopReplicationOnAutoRecovery) { $DomainSummary['StopReplicationOnAutoRecovery'] = [bool] $Registry.StopReplicationOnAutoRecovery } else { $DomainSummary['StopReplicationOnAutoRecovery'] = $null # $DomainSummary['StopReplicationOnAutoRecovery'] = $ErrorMessage } $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-WinADDFSTopology { <# .SYNOPSIS This command gets the DFS topology for a forest, listing it's current members .DESCRIPTION This command gets the DFS topology for a forest, listing it's current members. It can be used to find broken DFS members, which then can be removed using Remove-WinADDFSTopology .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 Type Type of objects to return (MissingAtLeastOne, MissingAll, All) .EXAMPLE Get-WinADDFSTopology | ft -AutoSize .NOTES General notes #> [cmdletbinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [ValidateSet('MissingAtLeastOne', 'MissingAll', 'All')][string] $Type = 'All' ) Write-Verbose -Message "Get-WinADDFSTopology - Getting forest information" $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains $Properties = @( 'Name', 'msDFSR-ComputerReference', 'msDFSR-MemberReferenceBL', 'ProtectedFromAccidentalDeletion', 'serverReference', 'WhenChanged', 'WhenCreated', 'DistinguishedName' ) foreach ($Domain in $ForestInformation.Domains) { Write-Verbose -Message "Get-WinADDFSTopology - Getting topology for $Domain" $DomainDN = ConvertTo-DistinguishedName -CanonicalName $Domain -ToDomain $QueryServer = $ForestInformation['QueryServers'][$Domain].HostName[0] $ObjectsInOu = Get-ADObject -LDAPFilter "(ObjectClass=msDFSR-Member)" -Properties $Properties -SearchBase "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,$DomainDN" -Server $QueryServer #$Data = $ObjectsInOu | Select-Object -Property Name, msDFSR-ComputerReference, msDFSR-MemberReferenceBL, ProtectedFromAccidentalDeletion, serverReference, WhenChanged, WhenCreated, DistinguishedName foreach ($Object in $ObjectsInOu) { if ($null -eq $Object.'msDFSR-ComputerReference' -and ($null -eq $Object.'msDFSR-MemberReferenceBL' -or $Object.'msDFSR-MemberReferenceBL'.Count -eq 0) -and $null -eq $Object.serverReference) { $Status = 'MissingAll' } elseif ($null -eq $Object.serverReference) { $Status = 'MissingAtLeastOne' } elseif ($null -eq $Object.'msDFSR-ComputerReference') { $Status = 'MissingAtLeastOne' } elseif ($null -eq $Object.'msDFSR-MemberReferenceBL' -or $Object.'msDFSR-MemberReferenceBL'.Count -eq 0) { $Status = 'MissingAtLeastOne' } else { $Status = 'OK' } $DataObject = [PSCustomObject] @{ 'Name' = $Object.Name 'Status' = $Status 'Domain' = $Domain 'msDFSR-ComputerReference' = $Object.'msDFSR-ComputerReference' 'msDFSR-MemberReferenceBL' = $Object.'msDFSR-MemberReferenceBL' 'ServerReference' = $Object.serverReference 'ProtectedFromAccidentalDeletion' = $Object.ProtectedFromAccidentalDeletion 'WhenChanged' = $Object.WhenChanged 'WhenCreated' = $Object.WhenCreated 'DistinguishedName' = $Object.DistinguishedName 'QueryServer' = $QueryServer } if ($Type -eq 'MissingAll') { if ($Status -eq 'MissingAll') { $DataObject } } elseif ($Type -eq 'MissingAtLeastOne') { if ($Status -in 'MissingAll', 'MissingAtLeastOne') { $DataObject } } else { $DataObject } } } } 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 { <# .SYNOPSIS Short description .DESCRIPTION Long description .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 .EXAMPLE An example .NOTES General notes #> [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 ) <# Levels 0 (None): Only critical events and error events are logged at this level. This is the default setting for all entries, and it should be modified only if a problem occurs that you want to investigate. 1 (Minimal): Very high-level events are recorded in the event log at this setting. Events may include one message for each major task that is performed by the service. Use this setting to start an investigation when you do not know the location of the problem. 2 (Basic) 3 (Extensive): This level records more detailed information than the lower levels, such as steps that are performed to complete a task. Use this setting when you have narrowed the problem to a service or a group of categories. 4 (Verbose) 5 (Internal): This level logs all events, including debug strings and configuration changes. A complete log of the service is recorded. Use this setting when you have traced the problem to a particular category of a small set of categories. #> $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-WinADDnsInformation { [CmdLetBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers, [string] $Splitter, [System.Collections.IDictionary] $ExtendedForestInformation ) if ($null -eq $TypesRequired) { #Write-Verbose 'Get-WinADDomainInformation - TypesRequired is null. Getting all.' #$TypesRequired = Get-Types -Types ([PSWinDocumentation.ActiveDirectory]) } # Gets all types # This queries AD ones for Forest/Domain/DomainControllers, passing this value to commands can help speed up discovery $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation $DNSServers = @{ } foreach ($Computer in $ForestInformation.ForestDomainControllers.HostName) { #try { # $DNSServer = Get-DNSServer -ComputerName $Computer #} catch { # #} $Data = [ordered] @{ } $Data.ServerCache = Get-WinDnsServerCache -ComputerName $Computer $Data.ServerClientSubnets = Get-DnsServerClientSubnet -ComputerName $Computer # TODO $Data.ServerDiagnostics = Get-WinDnsServerDiagnostics -ComputerName $Computer $Data.ServerDirectoryPartition = Get-WinDnsServerDirectoryPartition -ComputerName $Computer -Splitter $Splitter $Data.ServerDsSetting = Get-WinDnsServerDsSetting -ComputerName $Computer $Data.ServerEdns = Get-WinDnsServerEDns -ComputerName $Computer $Data.ServerForwarder = Get-WinADDnsServerForwarder -ComputerName $Computer -ExtendedForestInformation $ForestInformation -Formatted -Splitter $Splitter $Data.ServerGlobalNameZone = Get-WinDnsServerGlobalNameZone -ComputerName $Computer $Data.ServerGlobalQueryBlockList = Get-WinDnsServerGlobalQueryBlockList -ComputerName $Computer -Splitter $Splitter # $Data.ServerPolicies = $DNSServer.ServerPolicies $Data.ServerRecursion = Get-WinDnsServerRecursion -ComputerName $Computer $Data.ServerRecursionScopes = Get-WinDnsServerRecursionScope -ComputerName $Computer $Data.ServerResponseRateLimiting = Get-WinDnsServerResponseRateLimiting -ComputerName $Computer $Data.ServerResponseRateLimitingExceptionlists = Get-DnsServerResponseRateLimitingExceptionlist -ComputerName $Computer # TODO $Data.ServerRootHint = Get-WinDnsRootHint -ComputerName $Computer $Data.ServerScavenging = Get-WinADDnsServerScavenging -ComputerName $Computer $Data.ServerSetting = Get-WinDnsServerSettings -ComputerName $Computer # $Data.ServerZone = Get-DnsServerZone -ComputerName $Computer # problem # $Data.ServerZoneAging = Get-DnsServerZoneAging -ComputerName $Computer # problem # $Data.ServerZoneScope = Get-DnsServerZoneScope -ComputerName $Computer # problem # $Data.ServerDnsSecZoneSetting = Get-DnsServerDnsSecZoneSetting -ComputerName $Computer # problem $Data.VirtualizedServer = $DNSServer.VirtualizedServer $Data.VirtualizationInstance = Get-WinDnsServerVirtualizationInstance -ComputerName $Computer $DNSServers.$Computer = $Data } return $DNSServers } function Get-WinADDnsIPAddresses { <# .SYNOPSIS Gets all the DNS records from all the zones within a forest sorted by IPAddress .DESCRIPTION Gets all the DNS records from all the zones within a forest sorted by IPAddress .PARAMETER IncludeZone Limit the output of DNS records to specific zones .PARAMETER ExcludeZone Limit the output of dNS records to only zones not in the exclude list .PARAMETER IncludeDetails Adds additional information such as creation time, changed time .PARAMETER Prettify Converts arrays into strings connected with comma .PARAMETER IncludeDNSRecords Include full DNS records just in case one would like to further process them .PARAMETER AsHashtable Outputs the results as a hashtable instead of an array .EXAMPLE Get-WinADDnsIPAddresses | Format-Table * .EXAMPLE Get-WinADDnsIPAddresses -Prettify | Format-Table * .EXAMPLE Get-WinADDnsIPAddresses -Prettify -IncludeDetails -IncludeDNSRecords | Format-Table * .NOTES General notes #> [alias('Get-WinDnsIPAddresses')] [cmdletbinding()] param( [string[]] $IncludeZone, [string[]] $ExcludeZone, [switch] $IncludeDetails, [switch] $Prettify, [switch] $IncludeDNSRecords, [switch] $AsHashtable ) $DNSRecordsCached = [ordered] @{} $DNSRecordsPerZone = [ordered] @{} $ADRecordsPerZone = [ordered] @{} try { $oRootDSE = Get-ADRootDSE -ErrorAction Stop } catch { Write-Warning -Message "Get-WinADDnsIPAddresses - Could not get the root DSE. Make sure you're logged in to machine with Active Directory RSAT tools installed, and there's connecitivity to the domain. Error: $($_.Exception.Message)" return } $ADServer = ($oRootDSE.dnsHostName) $Exclusions = 'DomainDnsZones', 'ForestDnsZones', '@' $DNS = Get-DnsServerZone -ComputerName $ADServer [Array] $ZonesToProcess = foreach ($Zone in $DNS) { if ($Zone.ZoneType -eq 'Primary' -and $Zone.IsDsIntegrated -eq $true -and $Zone.IsReverseLookupZone -eq $false) { if ($Zone.ZoneName -notlike "*_*" -and $Zone.ZoneName -ne 'TrustAnchors') { if ($IncludeZone -and $IncludeZone -notcontains $Zone.ZoneName) { continue } if ($ExcludeZone -and $ExcludeZone -contains $Zone.ZoneName) { continue } $Zone } } } foreach ($Zone in $ZonesToProcess) { Write-Verbose -Message "Get-WinADDnsIPAddresses - Processing zone for DNS records: $($Zone.ZoneName)" $DNSRecordsPerZone[$Zone.ZoneName] = Get-DnsServerResourceRecord -ComputerName $ADServer -ZoneName $Zone.ZoneName -RRType A } if ($IncludeDetails) { $Filter = "(Name -notlike '@' -and Name -notlike '_*' -and ObjectClass -eq 'dnsNode' -and Name -ne 'ForestDnsZone' -and Name -ne 'DomainDnsZone' )" #$Filter = { (Name -notlike "@" -and Name -notlike "_*" -and ObjectClass -eq 'dnsNode' -and Name -ne 'ForestDnsZone' -and Name -ne 'DomainDnsZone' ) } foreach ($Zone in $ZonesToProcess) { $ADRecordsPerZone[$Zone.ZoneName] = [ordered]@{} Write-Verbose -Message "Get-WinADDnsIPAddresses - Processing zone for AD records: $($Zone.ZoneName)" $TempObjects = @( if ($Zone.ReplicationScope -eq 'Domain') { try { Get-ADObject -Server $ADServer -Filter $Filter -SearchBase ("DC=$($Zone.ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones," + $oRootDSE.defaultNamingContext) -Properties CanonicalName, whenChanged, whenCreated, DistinguishedName, ProtectedFromAccidentalDeletion, dNSTombstoned } catch { Write-Warning -Message "Get-WinADDnsIPAddresses - Error getting AD records for DomainDnsZones zone: $($Zone.ZoneName). Error: $($_.Exception.Message)" } } elseif ($Zone.ReplicationScope -eq 'Forest') { try { Get-ADObject -Server $ADServer -Filter $Filter -SearchBase ("DC=$($Zone.ZoneName),CN=MicrosoftDNS,DC=ForestDnsZones," + $oRootDSE.defaultNamingContext) -Properties CanonicalName, whenChanged, whenCreated, DistinguishedName, ProtectedFromAccidentalDeletion, dNSTombstoned } catch { Write-Warning -Message "Get-WinADDnsIPAddresses - Error getting AD records for ForestDnsZones zone: $($Zone.ZoneName). Error: $($_.Exception.Message)" } } else { Write-Warning -Message "Get-WinADDnsIPAddresses - Unknown replication scope: $($Zone.ReplicationScope)" } ) foreach ($DNSObject in $TempObjects) { $ADRecordsPerZone[$Zone.ZoneName][$DNSObject.Name] = $DNSObject } } } foreach ($Zone in $DNSRecordsPerZone.PSBase.Keys) { foreach ($Record in $DNSRecordsPerZone[$Zone]) { if ($Record.HostName -in $Exclusions) { continue } if (-not $DNSRecordsCached[$Record.RecordData.IPv4Address]) { $DNSRecordsCached[$Record.RecordData.IPv4Address] = [ordered] @{ IPAddress = $Record.RecordData.IPv4Address DnsNames = [System.Collections.Generic.List[Object]]::new() Timestamps = [System.Collections.Generic.List[Object]]::new() Types = [System.Collections.Generic.List[Object]]::new() Count = 0 } if ($ADRecordsPerZone.Keys.Count -gt 0) { $DNSRecordsCached[$Record.RecordData.IPv4Address].WhenCreated = $ADRecordsPerZone[$Zone][$Record.HostName].whenCreated $DNSRecordsCached[$Record.RecordData.IPv4Address].WhenChanged = $ADRecordsPerZone[$Zone][$Record.HostName].whenChanged } if ($IncludeDNSRecords) { $DNSRecordsCached[$Record.RecordData.IPv4Address].List = [System.Collections.Generic.List[Object]]::new() } } $DNSRecordsCached[$Record.RecordData.IPv4Address].DnsNames.Add($Record.HostName + "." + $Zone) if ($IncludeDNSRecords) { $DNSRecordsCached[$Record.RecordData.IPv4Address].List.Add($Record) } if ($null -ne $Record.TimeStamp) { $DNSRecordsCached[$Record.RecordData.IPv4Address].Timestamps.Add($Record.TimeStamp) } else { $DNSRecordsCached[$Record.RecordData.IPv4Address].Timestamps.Add("Not available") } if ($Null -ne $Record.Timestamp) { $DNSRecordsCached[$Record.RecordData.IPv4Address].Types.Add('Dynamic') } else { $DNSRecordsCached[$Record.RecordData.IPv4Address].Types.Add('Static') } $DNSRecordsCached[$Record.RecordData.IPv4Address] = [PSCustomObject] $DNSRecordsCached[$Record.RecordData.IPv4Address] } } foreach ($DNS in $DNSRecordsCached.PSBase.Keys) { $DNSRecordsCached[$DNS].Count = $DNSRecordsCached[$DNS].DnsNames.Count if ($Prettify) { $DNSRecordsCached[$DNS].DnsNames = $DNSRecordsCached[$DNS].DnsNames -join ", " $DNSRecordsCached[$DNS].Timestamps = $DNSRecordsCached[$DNS].Timestamps -join ", " $DNSRecordsCached[$DNS].Types = $DNSRecordsCached[$DNS].Types -join ", " } } if ($AsHashtable) { $DNSRecordsCached } else { $DNSRecordsCached.Values } } function Get-WinADDnsRecords { <# .SYNOPSIS Gets all the DNS records from all the zones within a forest .DESCRIPTION Gets all the DNS records from all the zones within a forest .PARAMETER IncludeZone Limit the output of DNS records to specific zones .PARAMETER ExcludeZone Limit the output of dNS records to only zones not in the exclude list .PARAMETER IncludeDetails Adds additional information such as creation time, changed time .PARAMETER Prettify Converts arrays into strings connected with comma .PARAMETER IncludeDNSRecords Include full DNS records just in case one would like to further process them .PARAMETER AsHashtable Outputs the results as a hashtable instead of an array .EXAMPLE Get-WinDNSRecords -Prettify -IncludeDetails | Format-Table .EXAMPLE $Output = Get-WinDNSRecords -Prettify -IncludeDetails -Verbose $Output.Count $Output | Sort-Object -Property Count -Descending | Select-Object -First 30 | Format-Table .NOTES General notes #> [alias('Get-WinDNSRecords')] [cmdletbinding()] param( [string[]] $IncludeZone, [string[]] $ExcludeZone, [switch] $IncludeDetails, [switch] $Prettify, [switch] $IncludeDNSRecords, [switch] $AsHashtable ) $DNSRecordsCached = [ordered] @{} $DNSRecordsPerZone = [ordered] @{} $ADRecordsPerZone = [ordered] @{} $ADRecordsPerZoneByDns = [ordered] @{} try { $oRootDSE = Get-ADRootDSE -ErrorAction Stop } catch { Write-Warning -Message "Get-WinDNSRecords - Could not get the root DSE. Make sure you're logged in to machine with Active Directory RSAT tools installed, and there's connecitivity to the domain. Error: $($_.Exception.Message)" return } $ADServer = ($oRootDSE.dnsHostName) $Exclusions = 'DomainDnsZones', 'ForestDnsZones', '@' $DNS = Get-DnsServerZone -ComputerName $ADServer [Array] $ZonesToProcess = foreach ($Zone in $DNS) { if ($Zone.ZoneType -eq 'Primary' -and $Zone.IsDsIntegrated -eq $true -and $Zone.IsReverseLookupZone -eq $false) { if ($Zone.ZoneName -notlike "*_*" -and $Zone.ZoneName -ne 'TrustAnchors') { if ($IncludeZone -and $IncludeZone -notcontains $Zone.ZoneName) { continue } if ($ExcludeZone -and $ExcludeZone -contains $Zone.ZoneName) { continue } $Zone } } } foreach ($Zone in $ZonesToProcess) { Write-Verbose -Message "Get-WinDNSRecords - Processing zone for DNS records: $($Zone.ZoneName)" $DNSRecordsPerZone[$Zone.ZoneName] = Get-DnsServerResourceRecord -ComputerName $ADServer -ZoneName $Zone.ZoneName -RRType A $ADRecordsPerZoneByDns[$Zone.ZoneName] = [ordered] @{} foreach ($Record in $DNSRecordsPerZone[$Zone.ZoneName]) { if (-not $ADRecordsPerZoneByDns[$Zone.ZoneName][$Record.HostName]) { $ADRecordsPerZoneByDns[$Zone.ZoneName][$Record.HostName] = [System.Collections.Generic.List[Object]]::new() } $ADRecordsPerZoneByDns[$Zone.ZoneName][$Record.HostName].Add($Record) } } if ($IncludeDetails) { #$Filter = { (Name -notlike "@" -and Name -notlike "_*" -and ObjectClass -eq 'dnsNode' -and Name -ne 'ForestDnsZone' -and Name -ne 'DomainDnsZone' ) } $Filter = "(Name -notlike '@' -and Name -notlike '_*' -and ObjectClass -eq 'dnsNode' -and Name -ne 'ForestDnsZone' -and Name -ne 'DomainDnsZone' )" foreach ($Zone in $ZonesToProcess) { $ADRecordsPerZone[$Zone.ZoneName] = [ordered]@{} Write-Verbose -Message "Get-WinDNSRecords - Processing zone for AD records: $($Zone.ZoneName)" $TempObjects = @( if ($Zone.ReplicationScope -eq 'Domain') { try { $getADObjectSplat = @{ Server = $ADServer Filter = $Filter SearchBase = ("DC=$($Zone.ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones," + $oRootDSE.defaultNamingContext) Properties = 'CanonicalName', 'whenChanged', 'whenCreated', 'DistinguishedName', 'ProtectedFromAccidentalDeletion', 'dNSTombstoned', 'nTSecurityDescriptor' } Get-ADObject @getADObjectSplat } catch { Write-Warning -Message "Get-WinDNSRecords - Error getting AD records for DomainDnsZones zone: $($Zone.ZoneName). Error: $($_.Exception.Message)" } } elseif ($Zone.ReplicationScope -eq 'Forest') { try { $getADObjectSplat = @{ Server = $ADServer Filter = $Filter SearchBase = ("DC=$($Zone.ZoneName),CN=MicrosoftDNS,DC=ForestDnsZones," + $oRootDSE.defaultNamingContext) Properties = 'CanonicalName', 'whenChanged', 'whenCreated', 'DistinguishedName', 'ProtectedFromAccidentalDeletion', 'dNSTombstoned', 'nTSecurityDescriptor' } Get-ADObject @getADObjectSplat } catch { Write-Warning -Message "Get-WinDNSRecords - Error getting AD records for ForestDnsZones zone: $($Zone.ZoneName). Error: $($_.Exception.Message)" } } else { Write-Warning -Message "Get-WinDNSRecords - Unknown replication scope: $($Zone.ReplicationScope)" } ) foreach ($DNSObject in $TempObjects) { $ADRecordsPerZone[$Zone.ZoneName][$DNSObject.Name] = $DNSObject } } } # PSBase is required because of "Keys" DNS name foreach ($Zone in $ADRecordsPerZone.PSBase.Keys) { foreach ($RecordName in [string[]] $ADRecordsPerZone[$Zone].PSBase.Keys) { $ADDNSRecord = $ADRecordsPerZone[$Zone][$RecordName] [Array] $ListRecords = $ADRecordsPerZoneByDns[$Zone][$RecordName] if ($ADDNSRecord.Name -in $Exclusions) { continue } if (-not $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"]) { $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"] = [ordered] @{ 'HostName' = $ADDNSRecord.Name 'Zone' = $Zone 'Status' = if ($ADDNSRecord.dNSTombstoned -eq $true) { 'Tombstoned' } else { 'Active' } Owner = $ADDNSRecord.ntsecuritydescriptor.owner RecordIP = [System.Collections.Generic.List[Object]]::new() Types = [System.Collections.Generic.List[Object]]::new() Timestamps = [System.Collections.Generic.List[Object]]::new() Count = 0 } #if ($ADRecordsPerZone.Keys.Count -gt 0) { #$DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].dNSTombstoned = $ADRecordsPerZone[$Zone][$ADDNSRecord.Name].dNSTombstoned $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].WhenCreated = $ADRecordsPerZone[$Zone][$ADDNSRecord.Name].whenCreated $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].WhenChanged = $ADRecordsPerZone[$Zone][$ADDNSRecord.Name].whenChanged #} if ($IncludeDNSRecords) { $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].List = [System.Collections.Generic.List[Object]]::new() } } if ($ListRecords.Count -gt 0) { foreach ($Record in $ListRecords) { if ($IncludeDNSRecords) { $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].List.Add($Record) } if ($null -ne $Record.TimeStamp) { $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].Timestamps.Add($Record.TimeStamp) } else { $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].Timestamps.Add("Not available") } $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].RecordIP.Add($Record.RecordData.IPv4Address) if ($Null -ne $Record.Timestamp) { $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].Types.Add('Dynamic') } else { $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].Types.Add('Static') } } } else { $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].RecordIP.Add('Not available') $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].Types.Add('Not available') $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"].Timestamps.Add('Not available') } $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"] = [PSCustomObject] $DNSRecordsCached["$($ADDNSRecord.Name).$($Zone)"] } } foreach ($DNS in $DNSRecordsCached.PSBase.Keys) { $DNSRecordsCached[$DNS].Count = $DNSRecordsCached[$DNS].RecordIP.Count if ($Prettify) { $DNSRecordsCached[$DNS].Types = $DNSRecordsCached[$DNS].Types -join ", " $DNSRecordsCached[$DNS].RecordIP = $DNSRecordsCached[$DNS].RecordIP -join ", " $DNSRecordsCached[$DNS].Timestamps = $DNSRecordsCached[$DNS].Timestamps -join ", " } } if ($AsHashtable) { $DNSRecordsCached } else { $DNSRecordsCached.Values } } function Get-WinADDnsServerForwarder { [CmdLetBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers, [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 foreach ($Computer in $ForestInformation.ForestDomainControllers) { try { $DnsServerForwarder = Get-DnsServerForwarder -ComputerName $Computer.HostName -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning "Get-WinDnsServerForwarder - Error $ErrorMessage" continue } foreach ($_ in $DnsServerForwarder) { if ($Formatted) { [PSCustomObject] @{ IPAddress = $_.IPAddress.IPAddressToString -join $Splitter ReorderedIPAddress = $_.ReorderedIPAddress.IPAddressToString -join $Splitter EnableReordering = $_.EnableReordering Timeout = $_.Timeout UseRootHint = $_.UseRootHint ForwardersCount = ($_.IPAddress.IPAddressToString).Count GatheredFrom = $Computer.HostName GatheredDomain = $Computer.Domain } } else { [PSCustomObject] @{ IPAddress = $_.IPAddress.IPAddressToString ReorderedIPAddress = $_.ReorderedIPAddress.IPAddressToString EnableReordering = $_.EnableReordering Timeout = $_.Timeout UseRootHint = $_.UseRootHint ForwardersCount = ($_.IPAddress.IPAddressToString).Count GatheredFrom = $Computer.HostName GatheredDomain = $Computer.Domain } } } } } function Get-WinADDnsServerScavenging { [CmdLetBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [Array] $GPOs, [System.Collections.IDictionary] $ExtendedForestInformation ) # if ($Domain -and -not $ComputerName) { # $ComputerName = (Get-ADDomainController -Filter * -Server $Domain).HostName # } $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation #foreach ($Domain in $ForestInformation.ForestDomainControllers) { foreach ($Computer in $ForestInformation.ForestDomainControllers) { try { $DnsServerScavenging = Get-DnsServerScavenging -ComputerName $Computer.HostName -ErrorAction Stop } catch { [PSCustomObject] @{ NoRefreshInterval = $null RefreshInterval = $null ScavengingInterval = $null ScavengingState = $null LastScavengeTime = $null GatheredFrom = $Computer.HostName GatheredDomain = $Computer.Domain } continue } foreach ($_ in $DnsServerScavenging) { [PSCustomObject] @{ NoRefreshInterval = $_.NoRefreshInterval RefreshInterval = $_.RefreshInterval ScavengingInterval = $_.ScavengingInterval ScavengingState = $_.ScavengingState LastScavengeTime = $_.LastScavengeTime GatheredFrom = $Computer.HostName GatheredDomain = $Computer.Domain } } } } function Get-ADWinDnsServerZones { [alias('Get-WinDnsServerZones')] [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, [switch] $ReverseLookupZone, [switch] $PrimaryZone, [switch] $Forwarder, [string] $ZoneName ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { foreach ($Computer in $ForestInformation['DomainDomainControllers'][$Domain]) { $getDnsServerZoneSplat = @{ ComputerName = $Computer.HostName Name = $ZoneName } Remove-EmptyValue -Hashtable $getDnsServerZoneSplat $Zones = Get-DnsServerZone @getDnsServerZoneSplat -ErrorAction SilentlyContinue foreach ($_ in $Zones) { if ($ZoneName) { if ($ZoneName -ne $_.ZoneName) { continue } } if ($_.ZoneType -eq 'Primary') { $ZoneAging = Get-DnsServerZoneAging -Name $_.ZoneName -ComputerName $Computer.HostName $AgingEnabled = $ZoneAging.AgingEnabled $AvailForScavengeTime = $ZoneAging.AvailForScavengeTime $RefreshInterval = $ZoneAging.RefreshInterval $NoRefreshInterval = $ZoneAging.NoRefreshInterval $ScavengeServers = $ZoneAging.ScavengeServers } else { $AgingEnabled = $null $AvailForScavengeTime = $null $RefreshInterval = $null $NoRefreshInterval = $null $ScavengeServers = $null } if ($Forwarder) { if ($_.ZoneType -ne 'Forwarder') { continue } } elseif ($ReverseLookupZone -and $PrimaryZone) { if ($_.IsReverseLookupZone -ne $true -or $_.ZoneType -ne 'Primary') { continue } } elseif ($ReverseLookupZone) { if ($_.IsReverseLookupZone -ne $true) { continue } } elseif ($PrimaryZone) { if ($_.ZoneType -ne 'Primary' -or $_.IsReverseLookupZone -ne $false ) { continue } } [PSCustomObject] @{ 'ZoneName' = $_.'ZoneName' 'ZoneType' = $_.'ZoneType' 'IsPDC' = $Computer.IsPDC 'AgingEnabled' = $AgingEnabled 'AvailForScavengeTime' = $AvailForScavengeTime 'RefreshInterval' = $RefreshInterval 'NoRefreshInterval' = $NoRefreshInterval 'ScavengeServers' = $ScavengeServers 'MasterServers' = $_.MasterServers 'NotifyServers' = $_.'NotifyServers' 'SecondaryServers' = $_.'SecondaryServers' 'AllowedDcForNsRecordsAutoCreation' = $_.'AllowedDcForNsRecordsAutoCreation' 'DistinguishedName' = $_.'DistinguishedName' 'IsAutoCreated' = $_.'IsAutoCreated' 'IsDsIntegrated' = $_.'IsDsIntegrated' 'IsPaused' = $_.'IsPaused' 'IsReadOnly' = $_.'IsReadOnly' 'IsReverseLookupZone' = $_.'IsReverseLookupZone' 'IsShutdown' = $_.'IsShutdown' 'DirectoryPartitionName' = $_.'DirectoryPartitionName' 'DynamicUpdate' = $_.'DynamicUpdate' 'IgnorePolicies' = $_.'IgnorePolicies' 'IsSigned' = $_.'IsSigned' 'IsWinsEnabled' = $_.'IsWinsEnabled' 'Notify' = $_.'Notify' 'ReplicationScope' = $_.'ReplicationScope' 'SecureSecondaries' = $_.'SecureSecondaries' 'ZoneFile' = $_.'ZoneFile' 'GatheredFrom' = $Computer.HostName 'GatheredDomain' = $Domain } } } } } function Get-WinADDNSZones { [alias('Get-WinDNSZones')] [CmdletBinding()] param( ) try { $oRootDSE = Get-ADRootDSE -ErrorAction Stop } catch { Write-Warning -Message "Get-WinDNSZones - Could not get the root DSE. Make sure you're logged in to machine with Active Directory RSAT tools installed, and there's connecitivity to the domain. Error: $($_.Exception.Message)" return } $ADServer = ($oRootDSE.dnsHostName) $DNS = Get-DnsServerZone -ComputerName $ADServer foreach ($Zone in $DNS) { [PSCustomObject] @{ ZoneName = $Zone.ZoneName #: _msdcs.ad.evotec.xyz ZoneType = $Zone.ZoneType #: Primary DynamicUpdate = $Zone.DynamicUpdate #: Secure ReplicationScope = $Zone.ReplicationScope #: Forest DirectoryPartitionName = $Zone.DirectoryPartitionName #: ForestDnsZones.ad.evotec.xyz #: IsAutoCreated = $Zone.IsAutoCreated #: False IsDsIntegrated = $Zone.IsDsIntegrated #: True IsReadOnly = $Zone.IsReadOnly #: False IsReverseLookupZone = $Zone.IsReverseLookupZone #: False IsSigned = $Zone.IsSigned #: False IsPaused = $Zone.IsPaused #: False IsShutdown = $Zone.IsShutdown #: False IsWinsEnabled = $Zone.IsWinsEnabled #: False Notify = $Zone.Notify #: NotifyServers NotifyServers = $Zone.NotifyServers #: SecureSecondaries = $Zone.SecureSecondaries #: NoTransfer SecondaryServers = $Zone.SecondaryServers #: LastZoneTransferAttempt = $Zone.LastZoneTransferAttempt #: LastSuccessfulZoneTransfer = $Zone.LastSuccessfulZoneTransfer #: LastZoneTransferResult = $Zone.LastZoneTransferResult #: LastSuccessfulSOACheck = $Zone.LastSuccessfulSOACheck #: MasterServers = $Zone.MasterServers #: LocalMasters = $Zone.LocalMasters #: UseRecursion = $Zone.UseRecursion #: ForwarderTimeout = $Zone.ForwarderTimeout #: AllowedDcForNsRecordsAutoCreation = $Zone.AllowedDcForNsRecordsAutoCreation #: DistinguishedName = $Zone.DistinguishedName #: DC=_msdcs.ad.evotec.xyz,cn=MicrosoftDNS,DC=ForestDnsZones,DC=ad,DC=evotec,DC=xyz ZoneFile = $Zone.ZoneFile } } } 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-WinADDomainControllerGenerationId { <# .SYNOPSIS Provides information about the msDS-GenerationId of domain controllers .DESCRIPTION Provides information about the msDS-GenerationId of domain controllers .PARAMETER Forest Forest name to use for resolving. If not given it will use current forest. .PARAMETER ExcludeDomains Exclude specific domains from test .PARAMETER ExcludeDomainControllers Exclude specific domain controllers from test .PARAMETER IncludeDomains Include specific domains in test .PARAMETER IncludeDomainControllers Include specific domain controllers in test .PARAMETER SkipRODC Skip Read Only Domain Controllers when querying for information .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE $Output = Get-WinADDomainControllerGenerationId -IncludeDomainControllers 'dc1.ad.evotec.pl' $Output | Format-Table .NOTES For virtual machine snapshot resuming detection. This attribute represents the VM Generation ID. #> [CmdletBinding()] 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 ) $ForestDetails = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -SkipRODC:$SkipRODC.IsPresent -IncludeDomainControllers $IncludeDomainControllers -ExcludeDomainControllers $ExcludeDomainControllers foreach ($Domain in $ForestDetails.Domains) { foreach ($D in $ForestDetails.DomainDomainControllers[$Domain]) { Write-Verbose -Message "Get-MSDSGenerationID - Executing Get-ADObject $D.ComputerObjectDN -Server $D.HostName -Properties Name, SamAccountName, 'msDS-GenerationId'" try { $Data = Get-ADObject $D.DistinguishedName -Server $D.HostName -Properties Name, SamAccountName, 'msDS-GenerationId' -ErrorAction Stop $ErrorProvided = $null } catch { $ErrorProvided = $_.Exception.Message $Data = $null } if ($Data) { $GenerationID = $Data.'msDS-GenerationId' } else { $GenerationID = $null } if ($GenerationID) { $TranslatedGenerationID = ($GenerationID | ForEach-Object { $_.ToString("X2") }) -join '' #$TranslatedGenerationIDAlternative = [System.Convert]::ToHexString($GenerationID) } else { #$TranslatedGenerationIDAlternative = $null $TranslatedGenerationID = $null } [PSCustomObject] @{ HostName = $D.HostName Domain = $Domain Name = $D.Name SamAccountName = $Data.SamAccountName 'msDS-GenerationId' = $TranslatedGenerationID #'msDS-GenerationId' = $TranslatedGenerationIDAlternative Error = $ErrorProvided } } } } function Get-WinADDomainControllerOption { <# .SYNOPSIS Command to get the options of a domain controller .DESCRIPTION Command to get the options of a domain controller that uses the repadmin command Provides information about: - DISABLE_OUTBOUND_REPL: Disables outbound replication. - DISABLE_INBOUND_REPL: Disables inbound replication. - DISABLE_NTDSCONN_XLATE: Disables the translation of NTDSConnection objects. - DISABLE_SPN_REGISTRATION: Disables Service Principal Name (SPN) registration. - IS_GC: Sets or unsets the Global Catalog (GC) for the domain controller. .PARAMETER DomainController The domain controller to get the options from .EXAMPLE Get-WinADDomainControllerOption -DomainController 'AD1', 'AD2','AD3' | Format-Table * .NOTES General notes #> [CmdletBinding()] param( [parameter(Mandatory)][string[]] $DomainController ) foreach ($DC in $DomainController) { # Execute the repadmin command and capture the output Write-Verbose -Message "Get-WinADDomainControllerOption - Executing repadmin /options $DC" $AvailableOptions = $null $repadminOutput = & repadmin /options $DC if ($repadminOutput[0].StartsWith("Repadmin can't connect to a", $true, [System.Globalization.CultureInfo]::InvariantCulture)) { Write-Warning -Message "Get-WinADDomainControllerOption - Unable to connect to [$DC]. Error: $($_.Exception.Message)" } else { $AvailableOptions = $repadminOutput[0].Replace("Current DSA Options: ", "") } if ($AvailableOptions) { $Options = $AvailableOptions -split " " } else { $Options = @() } $Output = [ordered] @{ Name = $DC Status = if ($AvailableOptions) { $true } else { $false } Options = foreach ($O in $Options) { $Value = $O.Trim() if ($Value) { $Value } } } if ($Output.Options -contains 'IS_GC') { $Output['IsGlobalCatalog'] = $true } else { $Output['IsGlobalCatalog'] = $false } if ($Output.Options -contains 'IS_RODC') { $Output['IsReadOnlyDomainController'] = $true } else { $Output['IsReadOnlyDomainController'] = $false } if ($Output.Options -contains 'DISABLE_OUTBOUND_REPL') { $Output['DisabledOutboundReplication'] = $true } else { $Output['DisabledOutboundReplication'] = $false } if ($Output.Options -contains 'DISABLE_INBOUND_REPL') { $Output['DisabledInboundReplication'] = $true } else { $Output['DisabledInboundReplication'] = $false } if ($Output.Options -contains 'DISABLE_NTDSCONN_XLATE') { $Output['DisabledNTDSConnectionTranslation'] = $true } else { $Output['DisabledNTDSConnectionTranslation'] = $false } if ($Output.Options -contains 'DISABLE_SPN_REGISTRATION') { $Output['DisabledSPNRegistration'] = $true } else { $Output['DisabledSPNRegistration'] = $false } [PSCustomObject] $Output } } Function Get-WinADDuplicateObject { <# .SYNOPSIS Get duplicate objects in Active Directory (CNF: and CNF:0ACNF:) .DESCRIPTION Get duplicate objects in Active Directory (CNF: and CNF:0ACNF:) CNF stands for "Conflict". CNF objects are created when there is a naming conflict in the Active Directory. This usually happens during the replication process when two objects are created with the same name in different parts of the replication topology, and then a replication attempt is made. Active Directory resolves this by renaming one of the objects with a CNF prefix and a GUID. The object with the CNF name is usually the loser in the conflict resolution process. .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 PartialMatchDistinguishedName Limit results to specific DistinguishedName .PARAMETER IncludeObjectClass Limit results to specific ObjectClass .PARAMETER ExcludeObjectClass Exclude specific ObjectClass .PARAMETER Extended Provide extended information about the object .PARAMETER NoPostProcessing Do not post process the object, return as is from the AD .EXAMPLE Get-WinADDuplicateObject -Verbose | Format-Table .NOTES General notes #> [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 ) # Based on https://gallery.technet.microsoft.com/scriptcenter/Get-ADForestConflictObjects-4667fa37 $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended foreach ($Domain in $ForestInformation.Domains) { $DomainInformation = $ForestInformation.DomainsExtended[$Domain] Write-Verbose -Message "Get-WinADDuplicateObject - Processing $($Domain)" $Partitions = @( if ($Domain -eq $ForestInformation.Forest) { "CN=Configuration,$($ForestInformation['DomainsExtended'][$Domain].DistinguishedName)" if ($DomainInformation.SubordinateReferences -contains "DC=ForestDnsZones,$($ForestInformation['DomainsExtended'][$Domain].DistinguishedName)") { "DC=ForestDnsZones,$($ForestInformation['DomainsExtended'][$Domain].DistinguishedName)" } else { Write-Warning -Message "Get-WinADDuplicateObject - ForestDnsZones not found for domain '$Domain'. Skipping" } } # Domain Name $ForestInformation['DomainsExtended'][$Domain].DistinguishedName # DNS Name if ($DomainInformation.SubordinateReferences -contains "DC=DomainDnsZones,$($ForestInformation['DomainsExtended'][$Domain].DistinguishedName)") { "DC=DomainDnsZones,$($ForestInformation['DomainsExtended'][$Domain].DistinguishedName)" } else { Write-Warning -Message "Get-WinADDuplicateObject - DomainDnsZones not found for domain '$Domain'. Skipping" } ) $DC = $ForestInformation['QueryServers']["$Domain"].HostName[0] #Get conflict objects foreach ($Partition in $Partitions) { Write-Verbose -Message "Get-WinADDuplicateObject - Processing $($Domain) - $($Partition)" $getADObjectSplat = @{ #Filter = "*" LDAPFilter = "(|(cn=*\0ACNF:*)(ou=*CNF:*))" Properties = 'DistinguishedName', 'ObjectClass', 'DisplayName', 'SamAccountName', 'Name', 'ObjectCategory', 'WhenCreated', 'WhenChanged', 'ProtectedFromAccidentalDeletion', 'ObjectGUID' Server = $DC SearchScope = 'Subtree' } try { $Objects = Get-ADObject @getADObjectSplat -SearchBase $Partition -ErrorAction Stop } catch { Write-Warning -Message "Get-WinADDuplicateObject - Getting objects from domain '$Domain' / partition: '$Partition' failed. Error: $($Object.Exception.Message)" continue } foreach ($Object in $Objects) { # Lets allow users to filter on it if ($ExcludeObjectClass) { if ($ExcludeObjectClass -contains $Object.ObjectClass) { continue } } if ($IncludeObjectClass) { if ($IncludeObjectClass -notcontains $Object.ObjectClass) { continue } } if ($PartialMatchDistinguishedName) { if ($Object.DistinguishedName -notlike $PartialMatchDistinguishedName) { continue } } if ($NoPostProcessing) { $Object continue } $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $Object.DistinguishedName -ToDomainCN # Lets create separate objects for different purpoeses $ConflictObject = [ordered] @{ ConflictDN = $Object.DistinguishedName ConflictWhenChanged = $Object.WhenChanged DomainName = $DomainName ObjectClass = $Object.ObjectClass } $LiveObjectData = [ordered] @{ LiveDn = "N/A" LiveWhenChanged = "N/A" } $RestData = [ordered] @{ DisplayName = $Object.DisplayName Name = $Object.Name.Replace("`n", ' ') SamAccountName = $Object.SamAccountName ObjectCategory = $Object.ObjectCategory WhenCreated = $Object.WhenCreated WhenChanged = $Object.WhenChanged ProtectedFromAccidentalDeletion = $Object.ProtectedFromAccidentalDeletion ObjectGUID = $Object.ObjectGUID.Guid # Server used to query the object Server = $DC # Partition used to query the object SearchBase = $Partition } if ($Extended) { $LiveObject = $null $ConflictObject = $ConflictObject + $LiveObjectData + $RestData #See if we are dealing with a 'cn' conflict object if (Select-String -SimpleMatch "\0ACNF:" -InputObject $ConflictObject.ConflictDn) { #Split the conflict object DN so we can remove the conflict notation $SplitConfDN = $ConflictObject.ConflictDn -split "0ACNF:" #Remove the conflict notation from the DN and try to get the live AD object 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 { #Split the conflict object DN so we can remove the conflict notation for OUs $SplitConfDN = $ConflictObject.ConflictDn -split "CNF:" #Remove the conflict notation from the DN and try to get the live AD object 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-WinADDuplicateSPN { <# .SYNOPSIS Detects and lists duplicate Service Principal Names (SPNs) in the Active Directory Domain. .DESCRIPTION Detects and lists duplicate Service Principal Names (SPNs) in the Active Directory Domain. .PARAMETER All Returns all duplicate and non-duplicate SPNs. Default is to only return duplicate SPNs. .PARAMETER Exclude Provides ability to exclude specific SPNs from the duplicate detection. By default it excludes kadmin/changepw as with multiple forests it will happen for sure. .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 .EXAMPLE Get-WinADDuplicateSPN | Format-Table .EXAMPLE Get-WinADDuplicateSPN -All | Format-Table .NOTES General notes #> [CmdletBinding()] param( [switch] $All, [string[]] $Exclude, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [Parameter(ParameterSetName = 'Forest')][System.Collections.IDictionary] $ExtendedForestInformation ) $Excluded = @( 'kadmin/changepw' foreach ($Item in $Exclude) { $iTEM } ) $SPNCache = [ordered] @{} $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { Write-Verbose -Message "Get-WinADDuplicateSPN - Processing $Domain" $Objects = (Get-ADObject -LDAPFilter "ServicePrincipalName=*" -Properties ServicePrincipalName -Server $ForestInformation['QueryServers'][$domain]['HostName'][0]) Write-Verbose -Message "Get-WinADDuplicateSPN - Found $($Objects.Count) objects. Processing..." foreach ($Object in $Objects) { foreach ($SPN in $Object.ServicePrincipalName) { if (-not $SPNCache[$SPN]) { $SPNCache[$SPN] = [PSCustomObject] @{ Name = $SPN Duplicate = $false Count = 0 Excluded = $false List = [System.Collections.Generic.List[Object]]::new() } } if ($SPN -in $Excluded) { $SPNCache[$SPN].Excluded = $true } $SPNCache[$SPN].List.Add($Object) $SPNCache[$SPN].Count++ } } } Write-Verbose -Message "Get-WinADDuplicateSPN - Finalizing output. Processing..." foreach ($SPN in $SPNCache.Values) { if ($SPN.Count -gt 1 -and $SPN.Excluded -ne $true) { $SPN.Duplicate = $true } if ($All) { $SPN } else { if ($SPN.Duplicate) { $SPN } } } } 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' ) $Filter = 'Name -ne "AzureADKerberos" -and DNSHostName -like "*"' $DCs = Get-ADComputer -Server $QueryServer -SearchBase $ForestInformation['DomainsExtended'][$Domain].DomainControllersContainer -Filter $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 } $Options = Get-WinADDomainControllerOption -DomainController $DC.DNSHostName if ($Options.Options -contains 'DISABLE_OUTBOUND_REPL') { $DisabledOutboundReplication = $true } else { $DisabledOutboundReplication = $false } if ($Options.Options -contains 'DISABLE_INBOUND_REPL') { $DisabledInboundReplication = $true } else { $DisabledInboundReplication = $false } if ($Options.Options -contains "IS_GC") { $IsGlobalCatalog = $true } else { $IsGlobalCatalog = $false } if ($Options.Options -contains 'IS_RODC') { $IsReadOnlyDomainController = $true } else { $IsReadOnlyDomainController = $false } $Roles = [ordered] @{} $Roles['SchemaMaster'] = $ForestInformation.Forest.SchemaMaster $Roles['DomainNamingMaster'] = $ForestInformation.Forest.DomainNamingMaster $Roles['InfrastructureMaster'] = $ForestInformation.DomainsExtended[$Domain].InfrastructureMaster $Roles['RIDMaster'] = $ForestInformation.DomainsExtended[$Domain].RIDMaster $Roles['PDCEmulator'] = $ForestInformation.DomainsExtended[$Domain].PDCEmulator $DNS = Resolve-DnsName -DnsOnly -Name $DC.DNSHostName -ErrorAction SilentlyContinue -QuickTimeout -Verbose:$false if ($DNS) { $ResolvedIP4 = ($DNS | Where-Object { $_.Section -eq 'Answer' -and $_.Type -eq 'A' }).IPAddress $ResolvedIP6 = ($DNS | Where-Object { $_.Section -eq 'Answer' -and $_.Type -eq 'AAAA' }).IPAddress $DNSStatus = $true } else { $ResolvedIP4 = $null $ResolvedIP6 = $null $DNSStatus = $false } [PSCustomObject] @{ DNSHostName = $DC.DNSHostName DomainName = $Domain Enabled = $DC.Enabled DNSStatus = $DNSStatus IsGC = $IsGlobalCatalog IsRODC = $IsReadOnlyDomainController 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 DisabledOutboundReplication = $DisabledOutboundReplication DisabledInboundReplication = $DisabledInboundReplication Options = $Options.Options -join ', ' UseDESKeyOnly = $DC.UseDESKeyOnly SchemaMaster = if ($Roles['SchemaMaster'] -eq $DC.DNSHostName) { $true } else { $false } DomainNamingMaster = if ($Roles['DomainNamingMaster'] -eq $DC.DNSHostName) { $true } else { $false } InfrastructureMaster = if ($Roles['InfrastructureMaster'] -eq $DC.DNSHostName) { $true } else { $false } RIDMaster = if ($Roles['RIDMaster'] -eq $DC.DNSHostName) { $true } else { $false } PDCEmulator = if ($Roles['PDCEmulator'] -eq $DC.DNSHostName) { $true } else { $false } 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' $WindowsLapsProperties = 'msLAPS-Password' $OptionalFeatures = $(Get-ADOptionalFeature -Filter "*" -Server $QueryServer) $Optional = [ordered]@{ 'Recycle Bin Enabled' = $false 'Privileged Access Management Feature Enabled' = $false 'LAPS Enabled' = ($ComputerProperties.Name -contains $LapsProperties) 'Windows LAPS Enabled' = ($ComputerProperties.Name -contains $WindowsLapsProperties) } 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 #-ErrorVariable +ProcessErrors } 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-WinADForestReplicationSummary { [CmdletBinding(DefaultParameterSetName = 'Default')] param( [Parameter(ParameterSetName = 'InputContent')][string] $InputContent, [Parameter(ParameterSetName = 'FilePath')][string] $FilePath ) if ($InputContent) { $OutputRepadmin = $InputContent } elseif ($FilePath) { $OutputRepadmin = Get-Content -Path $FilePath -Raw } else { # Run repadmin and capture the output $OutputRepadmin = repadmin /replsummary /bysrc /bydest | Out-String } # Split the output into sections $sections = $OutputRepadmin -split "Source DSA|Destination DSA" $lines = $sections[1] -split "`r`n" $sourceData = foreach ($line in $lines) { if ($line -match '^Experienced the following operational errors trying to retrieve replication information') { break } if ($line -match '\S' -and $line -notmatch '^\s*largest') { if ($line -match "^\s*(?<DSA>\S+)\s+(?<Rest>.*)$") { Write-Verbose -Message "Processing line: $line" $DSA = $Matches.DSA # $rest = $Matches.Rest -split "\s+", 4 # split into 4 parts: LargestDelta, Fails, Total, Percentage and the rest $Rest = $Matches.Rest if ($rest -match ">60 days") { $RestSplitted = $Rest -split "\s+", 7 $LargestDelta = New-TimeSpan -Days 60 $Fails = $RestSplitted[2] $Total = $RestSplitted[4] $Percentage = $RestSplitted[5] $ReplicationError = $RestSplitted[6] $Type = "Source" } else { $RestSplitted = $Rest -split "\s+", 4 # split into 4 parts: LargestDelta, Fails, Total, Percentage and the rest $LargestDelta = ConvertTo-TimeSpanFromRepadmin -timeString $RestSplitted[0] $Fails = $RestSplitted[1] $Continue = $RestSplitted[3] $Continue = $Continue -split "\s{2,}" $Total = $Continue[0] $Percentage = $Continue[1] $ReplicationError = $Continue[2] if ($null -eq $ReplicationError) { $ReplicationError = "None" } $Type = "Source" } [PSCustomObject]@{ Server = $DSA LargestDelta = $LargestDelta Fails = if ($null -ne $Fails) { $Fails.Replace("/", "").Trim() } else { $null } Total = $Total PercentageError = $Percentage Type = $Type ReplicationError = $ReplicationError } } } } $lines = $sections[2] -split "`r`n" $destinationData = foreach ($line in $lines) { if ($line -match '^Experienced the following operational errors trying to retrieve replication information') { break } if ($line -match '\S' -and $line -notmatch '^\s*largest') { if ($line -match "^\s*(?<DSA>\S+)\s+(?<Rest>.*)$") { Write-Verbose -Message "Processing line: $line" $DSA = $Matches.DSA $Rest = $Matches.Rest if ($rest -match ">60 days") { $RestSplitted = $Rest -split "\s+", 7 $LargestDelta = New-TimeSpan -Days 60 $Fails = $RestSplitted[2] $Total = $RestSplitted[4] $Percentage = $RestSplitted[5] $ReplicationError = $RestSplitted[6] $Type = "Destination" } else { $RestSplitted = $Rest -split "\s+", 4 # split into 4 parts: LargestDelta, Fails, Total, Percentage and the rest $LargestDelta = ConvertTo-TimeSpanFromRepadmin -timeString $RestSplitted[0] $Fails = $RestSplitted[1] $Continue = $RestSplitted[3] $Continue = $Continue -split "\s{2,}" $Total = $Continue[0] $Percentage = $Continue[1] $ReplicationError = $Continue[2] if ($null -eq $ReplicationError) { $ReplicationError = "None" } $Type = "Destination" } [PSCustomObject]@{ Server = $DSA LargestDelta = $LargestDelta Fails = if ($null -ne $Fails) { $Fails.Replace("/", "").Trim() } else { $null } Total = $Total PercentageError = $Percentage Type = $Type ReplicationError = $ReplicationError } } } } # Combine the data from both sections $sourceData + $destinationData } function Get-WinADForestRoles { <# .SYNOPSIS Lists all the forest roles for the chosen forest. By default uses current forest. .DESCRIPTION Lists all the forest roles for the chosen forest. By default uses current 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 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 Formatted Returns objects in formatted way .PARAMETER Splitter Character to use as splitter/joiner in formatted output .EXAMPLE $Roles = Get-WinADForestRoles $Roles | ft * .NOTES General notes #> [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 ) <# Name : dLMemRejectPermsBL CommonName : ms-Exch-DL-Mem-Reject-Perms-BL Oid : 1.2.840.113556.1.2.293 Syntax : DN Description : IsSingleValued : False IsIndexed : False IsIndexedOverContainer : False IsInAnr : False IsOnTombstonedObject : False IsTupleIndexed : False IsInGlobalCatalog : True RangeLower : RangeUpper : IsDefunct : False Link : dLMemRejectPerms LinkId : 117 SchemaGuid : a8df73c3-c5ea-11d1-bbcb-0080c76670c0 #> $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 ) <# 'nTSecurityDescriptor' = $_.'nTSecurityDescriptor' LastKnownParent = $_.LastKnownParent instanceType = $_.InstanceType InterSiteTopologyGenerator = $_.InterSiteTopologyGenerator dSCorePropagationData = $_.dSCorePropagationData ReplicationSchedule = $_.ReplicationSchedule.RawSchedule -join ',' msExchServerSiteBL = $_.msExchServerSiteBL -join ',' siteObjectBL = $_.siteObjectBL -join ',' systemFlags = $_.systemFlags ObjectGUID = $_.ObjectGUID ObjectCategory = $_.ObjectCategory ObjectClass = $_.ObjectClass ScheduleHashingEnabled = $_.ScheduleHashingEnabled #> $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0] $Sites = Get-ADReplicationSite -Filter "*" -Properties * -Server $QueryServer foreach ($Site in $Sites) { [Array] $DCs = $ForestInformation.ForestDomainControllers | Where-Object { $_.Site -eq $Site.Name } [Array] $Subnets = ConvertFrom-DistinguishedName -DistinguishedName $Site.'Subnets' if ($Formatted) { [PSCustomObject] @{ 'Name' = $Site.Name #'Display Name' = $Site.'DisplayName' 'Description' = $Site.'Description' 'CanonicalName' = $Site.'CanonicalName' '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 #'DisplayName' = $Site.'DisplayName' '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') { # We only set it to false to IPV4, for IPV6 it will be null as we don't know $Subnet['Overlap'] = $false } if ($Cache[$Subnet.Name]) { $Subnet['Overlap'] = $true $Subnet['OverLapList'] = $Cache[$Subnet.Name] } else { } [PSCustomObject] $Subnet } } else { Get-ADSubnet -Subnets $SubnetsList } } 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. Works only with All switch .PARAMETER SelfOnly Returns only one object that's summary for the whole group. Works only with All switch .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] $CountMembers, [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) { # lets initialize our variables 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 = '' ParentGroupDN = '' GroupDomainName = $null DistinguishedName = $null Sid = $null } $CollectedGroups = [System.Collections.Generic.List[string]]::new() $Nesting = -1 } $Nesting++ # lets get our object $ADGroupName = Get-WinADObject -Identity $GroupName -IncludeGroupMembership if ($ADGroupName) { # we add DomainName to hashtable so we can easily find which group we're dealing with if (-not $Nested.IsPresent) { $InitialGroup.GroupName = $ADGroupName.Name $InitialGroup.DomainName = $ADGroupName.DomainName if ($AddSelf -or $SelfOnly) { # Since we want in final run add primary object to array we need to make sure we have it filled $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 } } # Lets cache our object $Script:WinADGroupMemberCache[$ADGroupName.DistinguishedName] = $ADGroupName if ($Circular -or $CollectedGroups -contains $ADGroupName.DistinguishedName) { Write-Verbose -Message "Get-WinADGroupMember - Group '$($ADGroupName.DistinguishedName)' has $($ADGroupName.Members.Count) members" [Array] $NestedMembers = foreach ($MyIdentity in $ADGroupName.Members) { if ($MyIdentity) { if ($Script:WinADGroupMemberCache[$MyIdentity]) { $Script:WinADGroupMemberCache[$MyIdentity] } else { $ADObject = Get-WinADObject -Identity $MyIdentity -IncludeGroupMembership # -Properties SamAccountName, DisplayName, Enabled, userAccountControl, ObjectSID $Script:WinADGroupMemberCache[$MyIdentity] = $ADObject $Script:WinADGroupMemberCache[$MyIdentity] } } else { Write-Verbose "Get-WinADGroupMember - Group '$($ADGroupName.DistinguishedName)' user skipped because it's null" } } [Array] $NestedMembers = foreach ($Member in $NestedMembers) { if ($CollectedGroups -notcontains $Member.DistinguishedName) { $Member } } $Circular = $null } else { Write-Verbose -Message "Get-WinADGroupMember - Group '$($ADGroupName.DistinguishedName)' has $($ADGroupName.Members.Count) members" [Array] $NestedMembers = foreach ($MyIdentity in $ADGroupName.Members) { if ($MyIdentity) { if ($Script:WinADGroupMemberCache[$MyIdentity]) { $Script:WinADGroupMemberCache[$MyIdentity] } else { $ADObject = Get-WinADObject -Identity $MyIdentity -IncludeGroupMembership $Script:WinADGroupMemberCache[$MyIdentity] = $ADObject $Script:WinADGroupMemberCache[$MyIdentity] } } else { Write-Verbose "Get-WinADGroupMember - Group '$($ADGroupName.DistinguishedName)' user skipped because it's null" } } } # This tracks amount of members for our groups 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 } } $DomainParentGroup = ConvertFrom-DistinguishedName -DistinguishedName $ADGroupName.DistinguishedName -ToDomainCN foreach ($NestedMember in $NestedMembers) { # for each member we either create new user or group, if group we will dive into nesting $CreatedObject = [ordered] @{ GroupName = $InitialGroup.GroupName Name = $NestedMember.name SamAccountName = $NestedMember.SamAccountName DomainName = $NestedMember.DomainName #ConvertFrom-DistinguishedName -DistinguishedName $NestedMember.DistinguishedName -ToDomainCN 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 ParentGroupDN = $ADGroupName.DistinguishedName 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 #-CountMembers:$CountMembers.IsPresent if ($null -ne $OutputFromGroup) { $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 ($Nesting -eq 0) { # If nesting is 0 this means we are ending our run if (-not $All) { # If not ALL it means User wants to receive only users. Basically Get-ADGroupMember -Recursive $Output | Sort-Object -Unique -Property DistinguishedName | Select-Object -Property $Properties } else { # User requested ALL if ($AddSelf -or $SelfOnly) { # User also wants summary object added if ($InitialGroup.DistinguishedName) { $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 } # To get total memebers for given group we need to add all members from all groups + direct members of a group $AllMembersForGivenGroup = @( # Scan all groups for members foreach ($DirectGroup in $MembersCache[$InitialGroup.DistinguishedName].DirectGroups) { $MembersCache[$DirectGroup.DistinguishedName].DirectMembers } # Scan all direct members of this group $MembersCache[$InitialGroup.DistinguishedName].DirectMembers # Scan all indirect members of this group $MembersCache[$InitialGroup.DistinguishedName].IndirectMembers ) } $InitialGroup['TotalMembers'] = @($AllMembersForGivenGroup | Sort-Object -Unique -Property DistinguishedName).Count if ($AdditionalStatistics -or $SelfOnly) { $NestingMax = @($Output.Nesting | Sort-Object -Unique -Descending)[0] $InitialGroup['NestingMax'] = if ($null -eq $NestingMax) { 0 } else { $NestingMax } $NestingObjectTypes = $Output.Where( { $_.Type -eq 'group' }, 'split') $NestingGroupTypes = $NestingObjectTypes[0].Where( { $_.GroupType -eq 'Security' }, 'split') #$InitialGroup['NestingOther'] = ($NestingObjectTypes[1]).Count $InitialGroup['NestingGroup'] = ($NestingObjectTypes[0]).Count $InitialGroup['NestingGroupSecurity'] = ($NestingGroupTypes[0]).Count $InitialGroup['NestingGroupDistribution'] = ($NestingGroupTypes[1]).Count } # Finally returning object we just built [PSCustomObject] $InitialGroup } if (-not $SelfOnly) { foreach ($Object in $Output) { if ($Object.Type -eq 'group') { # Object is a group, we add direct members, direct groups and other stuff $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 } # To get total memebers for given group we need to add all members from all groups + direct members of a group $AllMembersForGivenGroup = @( # Scan all groups for members foreach ($DirectGroup in $MembersCache[$Object.DistinguishedName].DirectGroups) { $MembersCache[$DirectGroup.DistinguishedName].DirectMembers } # Scan all direct members of this group $MembersCache[$Object.DistinguishedName].DirectMembers # Scan all indirect members of this group $MembersCache[$Object.DistinguishedName].IndirectMembers ) $Object.TotalMembers = @($AllMembersForGivenGroup | Sort-Object -Unique -Property DistinguishedName).Count # Finally returning object we just built $Object } else { # Object is not a group we push it as is $Object } } } } } else { # this is nested call so we want to get whatever it gives us $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 CircularDirect = $false CircularIndirect = $false #CrossForest = $false ParentGroup = '' ParentGroupDomain = '' ParentGroupDN = '' ObjectDomainName = $Object.DomainName DistinguishedName = $Object.Distinguishedname Sid = $Object.ObjectSID } $CollectedGroups = [System.Collections.Generic.List[string]]::new() $Nesting = -1 } $Nesting++ if ($Object) { # Lets cache our object $Script:WinADGroupObjectCache[$Object.DistinguishedName] = $Object if ($Circular -or $CollectedGroups -contains $Object.DistinguishedName) { [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.SamAccountName)" #$DomainParentGroup = ConvertFrom-DistinguishedName -DistinguishedName $Object.DistinguishedName -ToDomainCN $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 CircularDirect = $false CircularIndirect = $false #CrossForest = $false ParentGroup = $Object.name ParentGroupDomain = $Object.DomainName ParentGroupDN = $Object.DistinguishedName ObjectDomainName = $InitialObject.DomainName DistinguishedName = $NestedMember.DistinguishedName Sid = $NestedMember.ObjectSID } #if ($NestedMember.DomainName -notin $Script:WinADForestCache['Domains']) { # $CreatedObject['CrossForest'] = $true #} if ($NestedMember.ObjectClass -eq "group") { if ($Object.members -contains $NestedMember.DistinguishedName) { $Circular = $Object.DistinguishedName $CreatedObject['CircularDirect'] = $true } $CollectedGroups.Add($Object.DistinguishedName) if ($CollectedGroups -contains $NestedMember.DistinguishedName) { $CreatedObject['CircularIndirect'] = $true } [PSCustomObject] $CreatedObject Write-Verbose "Get-WinADGroupMemberOf - Going deeper with $($NestedMember.SamAccountName)" try { $OutputFromGroup = Get-WinADGroupMemberOf -Identity $NestedMember -Nesting $Nesting -Circular $Circular -InitialObject $InitialObject -CollectedGroups $CollectedGroups -Nested } catch { Write-Warning "Get-WinADGroupMemberOf - Going deeper with $($NestedMember.SamAccountName) failed $($_.Exception.Message)" } $OutputFromGroup } else { [PSCustomObject] $CreatedObject } } } } } End { if ($Output.Count -gt 0) { if ($Nesting -eq 0) { if ($AddSelf) { [PSCustomObject] $InitialObject } foreach ($MyObject in $Output) { $MyObject } } else { # this is nested call so we want to get whatever it gives us $Output } } } } function Get-WinADGroups { [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $PerDomain, [switch] $AddOwner ) $AllUsers = [ordered] @{} $AllContacts = [ordered] @{} $AllGroups = [ordered] @{} $CacheUsersReport = [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', 'SamAccountName', 'ObjectSID' #'Description', #'PasswordNeverExpires', 'PasswordNotRequired', 'PasswordExpired', 'UserPrincipalName', 'SamAccountName', 'CannotChangePassword', #'TrustedForDelegation', 'TrustedToAuthForDelegation', 'msExchMailboxGuid', 'msExchRemoteRecipientType', 'msExchRecipientTypeDetails', # 'msExchRecipientDisplayType', 'pwdLastSet', "msDS-UserPasswordExpiryTimeComputed", # 'WhenCreated', 'WhenChanged' ) $AllUsers[$Domain] = Get-ADUser -Filter "*" -Properties $Properties -Server $QueryServer #$ForestInformation['QueryServers'][$Domain].HostName[0] $AllContacts[$Domain] = Get-ADObject -Filter 'objectClass -eq "contact"' -Properties SamAccountName, Mail, Name, DistinguishedName, WhenChanged, Whencreated, DisplayName, ObjectSID -Server $QueryServer $Properties = @( 'SamAccountName', 'msExchRecipientDisplayType', 'msExchRecipientTypeDetails', 'CanonicalName', 'Mail', 'Description', 'Name', 'GroupScope', 'GroupCategory', 'DistinguishedName', 'isCriticalSystemObject', 'adminCount', 'WhenChanged', 'Whencreated', 'DisplayName', 'ManagedBy', 'member', 'memberof', 'ProtectedFromAccidentalDeletion', 'nTSecurityDescriptor', 'groupType' 'SID', 'SIDHistory', 'proxyaddresses', 'ObjectSID' ) $AllGroups[$Domain] = Get-ADGroup -Filter "*" -Properties $Properties -Server $QueryServer } foreach ($Domain in $AllUsers.Keys) { foreach ($U in $AllUsers[$Domain]) { $CacheUsersReport[$U.DistinguishedName] = $U } } foreach ($Domain in $AllContacts.Keys) { foreach ($C in $AllContacts[$Domain]) { $CacheUsersReport[$C.DistinguishedName] = $C } } foreach ($Domain in $AllGroups.Keys) { foreach ($G in $AllGroups[$Domain]) { $CacheUsersReport[$G.DistinguishedName] = $G } } $Output = [ordered] @{} foreach ($Domain in $ForestInformation.Domains) { $Output[$Domain] = foreach ($Group in $AllGroups[$Domain]) { $UserLocation = ($Group.DistinguishedName -split ',').Replace('OU=', '').Replace('CN=', '').Replace('DC=', '') $Region = $UserLocation[-4] $Country = $UserLocation[-5] if ($Group.ManagedBy) { $ManagerAll = $CacheUsersReport[$Group.ManagedBy] $Manager = $CacheUsersReport[$Group.ManagedBy].Name $ManagerSamAccountName = $CacheUsersReport[$Group.ManagedBy].SamAccountName $ManagerEmail = $CacheUsersReport[$Group.ManagedBy].Mail $ManagerEnabled = $CacheUsersReport[$Group.ManagedBy].Enabled $ManagerLastLogon = $CacheUsersReport[$Group.ManagedBy].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 { $ManagerAll = $null if ($Group.ObjectClass -eq 'user') { $ManagerStatus = 'Missing' } else { $ManagerStatus = 'Not available' } $Manager = $null $ManagerSamAccountName = $null $ManagerEmail = $null $ManagerEnabled = $null $ManagerLastLogon = $null $ManagerLastLogonDays = $null } $msExchRecipientTypeDetails = Convert-ExchangeRecipient -msExchRecipientTypeDetails $Group.msExchRecipientTypeDetails $msExchRecipientDisplayType = Convert-ExchangeRecipient -msExchRecipientDisplayType $Group.msExchRecipientDisplayType #$msExchRemoteRecipientType = Convert-ExchangeRecipient -msExchRemoteRecipientType $Group.msExchRemoteRecipientType if ($ManagerAll.ObjectSID) { $ACL = Get-ADACL -ADObject $Group -Resolve -Principal $ManagerAll.ObjectSID -IncludeObjectTypeName 'Self-Membership' -IncludeActiveDirectoryRights WriteProperty } else { $ACL = $null } # $GroupWriteback = $false # # https://practical365.com/azure-ad-connect-group-writeback-deep-dive/ # if ($Group.msExchRecipientDisplayType -eq 17) { # # M365 Security Group and M365 Mail-Enabled security Group # $GroupWriteback = $true # } else { # # if ($Group.GroupType -eq -2147483640 -and $Group.GroupCategory -eq 'Security' -and $Group.GroupScope -eq 'Universal') { # # $GroupWriteback = $true # # } else { # # $GroupWriteback = $false # # } # } if ($AddOwner) { $Owner = Get-ADACLOwner -ADObject $Group -Verbose -Resolve [PSCustomObject] @{ Name = $Group.Name #DisplayName = $Group.DisplayName CanonicalName = $Group.CanonicalName Domain = $Domain SamAccountName = $Group.SamAccountName MemberCount = if ($Group.member) { $Group.member.Count } else { 0 } GroupScope = $Group.GroupScope GroupCategory = $Group.GroupCategory #GroupWriteBack = $GroupWriteBack #ManagedBy = $Group.ManagedBy msExchRecipientTypeDetails = $msExchRecipientTypeDetails msExchRecipientDisplayType = $msExchRecipientDisplayType #msExchRemoteRecipientType = $msExchRemoteRecipientType Manager = $Manager ManagerCanUpdateGroupMembership = if ($ACL) { $true } else { $false } ManagerSamAccountName = $ManagerSamAccountName ManagerEmail = $ManagerEmail ManagerEnabled = $ManagerEnabled ManagerLastLogon = $ManagerLastLogon ManagerLastLogonDays = $ManagerLastLogonDays ManagerStatus = $ManagerStatus OwnerName = $Owner.OwnerName OwnerSID = $Owner.OwnerSID OwnerType = $Owner.OwnerType WhenCreated = $Group.WhenCreated WhenChanged = $Group.WhenChanged ProtectedFromAccidentalDeletion = $Group.ProtectedFromAccidentalDeletion ProxyAddresses = Convert-ExchangeEmail -Emails $Group.ProxyAddresses -RemoveDuplicates -RemovePrefix Description = $Group.Description DistinguishedName = $Group.DistinguishedName Level0 = $Region Level1 = $Country ManagerDN = $Group.ManagedBy } } else { [PSCustomObject] @{ Name = $Group.Name #DisplayName = $Group.DisplayName CanonicalName = $Group.CanonicalName Domain = $Domain SamAccountName = $Group.SamAccountName MemberCount = if ($Group.member) { $Group.member.Count } else { 0 } GroupScope = $Group.GroupScope GroupCategory = $Group.GroupCategory #GroupWriteBack = $GroupWriteBack #ManagedBy = $Group.ManagedBy msExchRecipientTypeDetails = $msExchRecipientTypeDetails msExchRecipientDisplayType = $msExchRecipientDisplayType #msExchRemoteRecipientType = $msExchRemoteRecipientType Manager = $Manager ManagerCanUpdateGroupMembership = if ($ACL) { $true } else { $false } ManagerSamAccountName = $ManagerSamAccountName ManagerEmail = $ManagerEmail ManagerEnabled = $ManagerEnabled ManagerLastLogon = $ManagerLastLogon ManagerLastLogonDays = $ManagerLastLogonDays ManagerStatus = $ManagerStatus WhenCreated = $Group.WhenCreated WhenChanged = $Group.WhenChanged ProtectedFromAccidentalDeletion = $Group.ProtectedFromAccidentalDeletion ProxyAddresses = Convert-ExchangeEmail -Emails $Group.ProxyAddresses -RemoveDuplicates -RemovePrefix Description = $Group.Description DistinguishedName = $Group.DistinguishedName Level0 = $Region Level1 = $Country ManagerDN = $Group.ManagedBy } } } } if ($PerDomain) { $Output } else { $Output.Values } } function Get-WinADKerberosAccount { [CmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $IncludeCriticalAccounts ) $Today = Get-Date $Accounts = [ordered] @{ 'CriticalAccounts' = [ordered] @{} 'Data' = [ordered] @{} } Write-Verbose -Message "Get-WinADKerberosAccount - Gathering information about forest" $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -PreferWritable foreach ($Domain in $ForestInformation.Domains) { $Accounts['Data']["$Domain"] = [ordered] @{} } $DomainCount = 0 $DomainCountTotal = $ForestInformation.Domains.Count foreach ($Domain in $ForestInformation.Domains) { $DomainCount++ $ProcessingText = "[Domain: $DomainCount/$DomainCountTotal]" Write-Verbose -Message "Get-WinADKerberosAccount - $ProcessingText Processing domain $Domain" $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] $Properties = @( 'Name', 'SamAccountName', 'msDS-KrbTgtLinkBl', 'Enabled', 'PasswordLastSet', 'WhenCreated', 'WhenChanged' 'AllowReversiblePasswordEncryption', 'BadLogonCount', 'AccountNotDelegated' 'SID', 'SIDHistory' ) $PropertiesMembers = @( 'Name', 'SamAccountName' 'Enabled', 'PasswordLastSet', 'WhenCreated', 'WhenChanged' 'AllowReversiblePasswordEncryption', 'BadLogonCount', 'AccountNotDelegated' 'SID', 'SIDHistory' ) $CountK = 0 try { [Array] $KerberosPasswords = Get-ADUser -Filter "Name -like 'krbtgt*'" -Server $QueryServer -Properties $Properties -ErrorAction Stop } catch { Write-Warning -Message "Get-WinADKerberosAccount - $ProcessingText Processing domain $Domain - unable to get Kerberos accounts. Error: $($_.Exception.Message)" continue } if ($IncludeCriticalAccounts) { $Members = @( try { Get-ADGroupMember -Identity 'Domain Admins' -Server $QueryServer -Recursive -ErrorAction Stop } catch { Write-Warning -Message "Get-WinADKerberosAccount - $ProcessingText Processing domain $Domain - unable to get Domain Admins. Error: $($_.Exception.Message)" } try { Get-ADGroupMember -Identity 'Enterprise Admins' -Server $QueryServer -Recursive -ErrorAction Stop } catch { Write-Warning -Message "Get-WinADKerberosAccount - $ProcessingText Processing domain $Domain - unable to get Enterprise Admins. Error: $($_.Exception.Message)" } ) | Sort-Object -Unique -Property DistinguishedName } else { $Members = @() } $CriticalAccounts = foreach ($Member in $Members) { Try { $User = Get-ADUser -Identity $Member.DistinguishedName -Server $QueryServer -Properties $PropertiesMembers -ErrorAction Stop } Catch { Write-Warning -Message "Get-WinADKerberosAccount - $ProcessingText Processing domain $Domain - unable to get critical account $($Member.DistinguishedName). Error: $($_.Exception.Message)" } if ($User) { if ($null -eq $User.WhenChanged) { $WhenChangedDaysAgo = $null } else { $WhenChangedDaysAgo = ($Today) - $User.WhenChanged } if ($null -eq $User.PasswordLastSet) { $PasswordLastSetAgo = $null } else { $PasswordLastSetAgo = ($Today) - $User.PasswordLastSet } [PSCustomObject] @{ 'Name' = $User.Name 'SamAccountName' = $User.SamAccountName 'Enabled' = $User.Enabled 'PasswordLastSet' = $User.PasswordLastSet 'PasswordLastSetDays' = $PasswordLastSetAgo.Days 'WhenChangedDays' = $WhenChangedDaysAgo.Days 'WhenChanged' = $User.WhenChanged 'WhenCreated' = $User.WhenCreated 'AllowReversiblePasswordEncryption' = $User.AllowReversiblePasswordEncryption 'BadLogonCount' = $User.BadLogonCount 'AccountNotDelegated' = $User.AccountNotDelegated 'SID' = $User.SID 'SIDHistory' = $User.SIDHistory } } } foreach ($Account in $KerberosPasswords) { $CountK++ $ProcessingText = "[Domain: $DomainCount/$DomainCountTotal / Account: $CountK/$($KerberosPasswords.Count)]" Write-Verbose -Message "Get-WinADKerberosAccount - $ProcessingText Processing domain $Domain \ Kerberos account ($CountK/$($KerberosPasswords.Count)) $($Account.SamAccountName) \ DC" #if ($Account.SamAccountName -like "*_*" -and -not $Account.'msDS-KrbTgtLinkBl') { # Write-Warning -Message "Get-WinADKerberosAccount - Processing domain $Domain \ Kerberos account $($Account.SamAccountName) \ DC - Skipping" # continue #} $CachedServers = [ordered] @{} $CountDC = 0 $CountDCTotal = $ForestInformation.DomainDomainControllers[$Domain].Count foreach ($DC in $ForestInformation.DomainDomainControllers[$Domain]) { $CountDC++ $Server = $DC.HostName $ProcessingText = "[Domain: $DomainCount/$DomainCountTotal / Account: $CountK/$($KerberosPasswords.Count), DC: $CountDC/$CountDCTotal]" Write-Verbose -Message "Get-WinADKerberosAccount - $ProcessingText Processing domain $Domain \ Kerberos account $($Account.SamAccountName) \ DC Server $Server" try { $ServerData = Get-ADUser -Identity $Account.DistinguishedName -Server $Server -Properties 'msDS-KrbTgtLinkBl', 'PasswordLastSet', 'WhenCreated', 'WhenChanged' -ErrorAction Stop } catch { Write-Warning -Message "Get-WinADKerberosAccount - Processing domain $Domain $ProcessingText \ Kerberos account $($Account.SamAccountName) \ DC Server $Server - Error: $($_.Exception.Message)" $CachedServers[$Server] = [PSCustomObject] @{ 'Server' = $Server 'Name' = $Server 'PasswordLastSet' = $null 'PasswordLastSetDays' = $null 'WhenChangedDays' = $null 'WhenChanged' = $null 'WhenCreated' = $null 'msDS-KrbTgtLinkBl' = $ServerData.'msDS-KrbTgtLinkBl' 'Status' = $_.Exception.Message } } if ($ServerData.Name) { if ($null -eq $ServerData.WhenChanged) { $WhenChangedDaysAgo = $null } else { $WhenChangedDaysAgo = ($Today) - $ServerData.WhenChanged } if ($null -eq $ServerData.PasswordLastSet) { $PasswordLastSetAgo = $null } else { $PasswordLastSetAgo = ($Today) - $ServerData.PasswordLastSet } if ($Account.SamAccountName -like "*_*" -and $ServerData.'msDS-KrbTgtLinkBl') { $Status = 'OK' } elseif ($Account.SamAccountName -like "*_*" -and -not $ServerData.'msDS-KrbTgtLinkBl') { $Status = 'Missing link, orphaned?' } else { $Status = 'OK' } $CachedServers[$Server] = [PSCustomObject] @{ 'Server' = $Server 'Name' = $ServerData.Name 'PasswordLastSet' = $ServerData.'PasswordLastSet' 'PasswordLastSetDays' = $PasswordLastSetAgo.Days 'WhenChangedDays' = $WhenChangedDaysAgo.Days 'WhenChanged' = $ServerData.'WhenChanged' 'WhenCreated' = $ServerData.'WhenCreated' 'msDS-KrbTgtLinkBl' = $ServerData.'msDS-KrbTgtLinkBl' 'Status' = $Status } } } Write-Verbose -Message "Get-WinADKerberosAccount - Gathering information about forest for Global Catalogs" $ForestInformationGC = Get-WinADForestDetails -Forest $Forest $ProcessingText = "[Domain: $DomainCount/$DomainCountTotal / Account: $CountK/$($KerberosPasswords.Count)]" Write-Verbose -Message "Get-WinADKerberosAccount - $ProcessingText Processing domain $Domain \ Kerberos account $($Account.SamAccountName) \ GC" $GlobalCatalogs = [ordered] @{} $GlobalCatalogCount = 0 $GlobalCatalogCountTotal = $ForestInformationGC.ForestDomainControllers.Count foreach ($DC in $ForestInformationGC.ForestDomainControllers) { $GlobalCatalogCount++ $Server = $DC.HostName $ProcessingText = "[Domain: $DomainCount/$DomainCountTotal / Account: $CountK/$($KerberosPasswords.Count), GC: $GlobalCatalogCount/$GlobalCatalogCountTotal]" Write-Verbose -Message "Get-WinADKerberosAccount - $ProcessingText Processing domain $Domain \ Kerberos account $($Account.SamAccountName) \ GC Server $Server" if ($DC.IsGlobalCatalog ) { try { $ServerData = Get-ADUser -Identity $Account.DistinguishedName -Server "$($Server):3268" -Properties 'msDS-KrbTgtLinkBl', 'PasswordLastSet', 'WhenCreated', 'WhenChanged' -ErrorAction Stop } catch { Write-Warning -Message "Get-WinADKerberosAccount - Processing domain $Domain $ProcessingText \ Kerberos account $($Account.SamAccountName) \ GC Server $Server - Error: $($_.Exception.Message)" $GlobalCatalogs[$Server] = [PSCustomObject] @{ 'Server' = $Server 'Name' = $Server 'PasswordLastSet' = $null 'PasswordLastSetDays' = $null 'WhenChangedDays' = $null 'WhenChanged' = $null 'WhenCreated' = $null 'msDS-KrbTgtLinkBl' = $null 'Status' = $_.Exception.Message } } if ($ServerData.Name) { if ($null -eq $ServerData.WhenChanged) { $WhenChangedDaysAgo = $null } else { $WhenChangedDaysAgo = ($Today) - $ServerData.WhenChanged } if ($null -eq $ServerData.PasswordLastSet) { $PasswordLastSetAgo = $null } else { $PasswordLastSetAgo = ($Today) - $ServerData.PasswordLastSet } $GlobalCatalogs[$Server] = [PSCustomObject] @{ 'Server' = $Server 'Name' = $ServerData.Name 'PasswordLastSet' = $ServerData.'PasswordLastSet' 'PasswordLastSetDays' = $PasswordLastSetAgo.Days 'WhenChangedDays' = $WhenChangedDaysAgo.Days 'WhenChanged' = $ServerData.'WhenChanged' 'WhenCreated' = $ServerData.'WhenCreated' 'msDS-KrbTgtLinkBl' = $ServerData.'msDS-KrbTgtLinkBl' 'Status' = 'OK' } } } } if ($null -eq $Account.PasswordLastSet) { $PasswordLastSetAgo = $null } else { $PasswordLastSetAgo = ($Today) - $Account.PasswordLastSet } if ($null -eq $Account.WhenChanged) { $WhenChangedDaysAgo = $null } else { $WhenChangedDaysAgo = ($Today) - $Account.WhenChanged } $Accounts['Data']["$Domain"][$Account.SamAccountName] = @{ FullInformation = [PSCustomObject] @{ 'Name' = $Account.Name 'SamAccountName' = $Account.SamAccountName 'Enabled' = $Account.Enabled 'PasswordLastSet' = $Account.PasswordLastSet 'PasswordLastSetDays' = $PasswordLastSetAgo.Days 'WhenChangedDays' = $WhenChangedDaysAgo.Days 'WhenChanged' = $Account.WhenChanged 'WhenCreated' = $Account.WhenCreated 'AllowReversiblePasswordEncryption' = $Account.AllowReversiblePasswordEncryption 'BadLogonCount' = $Account.BadLogonCount 'AccountNotDelegated' = $Account.AccountNotDelegated 'SID' = $Account.SID 'SIDHistory' = $Account.SIDHistory } DomainControllers = $CachedServers GlobalCatalogs = $GlobalCatalogs } } $Accounts['CriticalAccounts']["$Domain"] = $CriticalAccounts } $Accounts } 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 <# auditbasedirectories : 0 auditbaseobjects : 0 Bounds : {0, 48, 0, 0...} crashonauditfail : 0 fullprivilegeauditing : {0} LimitBlankPasswordUse : 1 NoLmHash : 1 disabledomaincreds : 0 everyoneincludesanonymous : 0 forceguest : 0 LsaCfgFlagsDefault : 0 LsaPid : 1232 ProductType : 4 restrictanonymous : 0 restrictanonymoussam : 1 SecureBoot : 1 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 # https://docs.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection Level = $LMCompatibilityLevel LevelDescription = $LM[$LMCompatibilityLevel] EveryoneIncludesAnonymous = [bool] $LSA.everyoneincludesanonymous LimitBlankPasswordUse = [bool] $LSA.LimitBlankPasswordUse NoLmHash = [bool] $LSA.NoLmHash DisableDomainCreds = [bool] $LSA.disabledomaincreds # https://www.stigviewer.com/stig/windows_8/2014-01-07/finding/V-3376 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 # https://www.stigviewer.com/stig/windows_server_2012_member_server/2014-01-07/finding/V-14228 | Should be false CrashOnAuditFail = $LSA.CrashOnAuditFail # http://systemmanager.ru/win2k_regestry.en/46686.htm | Should be 0 } } 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 Get-WinADObject -Identity 'TEST\Domain Admins' -Verbose Get-WinADObject -Identity 'EVOTEC\Domain Admins' -Verbose Get-WinADObject -Identity 'Domain Admins' -DomainName 'DC=AD,DC=EVOTEC,DC=PL' -Verbose Get-WinADObject -Identity 'Domain Admins' -DomainName 'ad.evotec.pl' -Verbose Get-WinADObject -Identity 'CN=Domain Admins,CN=Users,DC=ad,DC=evotec,DC=pl' Get-WinADObject -Identity 'CN=Domain Admins,CN=Users,DC=ad,DC=evotec,DC=xyz' .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 = @{} } # This is purely for calling group workaround Add-Type -AssemblyName System.DirectoryServices.AccountManagement $GroupTypes = @{ '2' = @{ Name = 'Distribution Group - Global' # distribution Type = 'Distribution' Scope = 'Global' } '4' = @{ Name = 'Distribution Group - Domain Local' # distribution 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' # Builtin local Security Group Type = 'Security' Scope = 'Builtin local' } '-2147483644' = @{ Name = 'Security Group - Domain Local' Type = 'Security' Scope = 'Domain local' } '-2147483646' = @{ Name = 'Security Group - Global' # security Type = 'Security' Scope = 'Global' } } } process { foreach ($Ident in $Identity) { if (-not $Ident) { Write-Warning -Message "Get-WinADObject - Identity is empty. Skipping" continue } $ResolvedIdentity = $null # If it's an object we need to make sure we pass only DN if ($Ident.DistinguishedName) { $Ident = $Ident.DistinguishedName } # we reset domain name to it's given value if at all $TemporaryName = $Ident $TemporaryDomainName = $DomainName # Since we change $Ident below to different names we need to be sure we use original query for cache if ($Cache -and $Script:CacheObjectsWinADObject[$TemporaryName]) { Write-Verbose "Get-WinADObject - Requesting $TemporaryName from Cache" $Script:CacheObjectsWinADObject[$TemporaryName] continue } # if Domain Name is provided we don't check for anything as it's most likely already good Ident value 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 -Verbose:$false 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 '*DC=*') { $DNConversion = ConvertFrom-DistinguishedName -DistinguishedName $Ident -ToDomainCN $TemporaryDomainName = $DNConversion } elseif ($Ident -like '*@*') { $CNConversion = $Ident -split '@', 2 $TemporaryDomainName = $CNConversion[1] $Ident = $CNConversion[0] } elseif ($Ident -like '*.*') { $ResolvedIdentity = Convert-Identity -Identity $Ident -Verbose:$false if ($ResolvedIdentity.SID) { $TemporaryDomainName = $ResolvedIdentity.DomainName $Ident = $ResolvedIdentity.SID } else { $CNConversion = $Ident -split '\.', 2 $Ident = $CNConversion[0] $TemporaryDomainName = $CNConversion[1] } } else { $ResolvedIdentity = Convert-Identity -Identity $Ident -Verbose:$false if ($ResolvedIdentity.SID) { $TemporaryDomainName = $ResolvedIdentity.DomainName $Ident = $ResolvedIdentity.SID } else { $NetbiosConversion = ConvertFrom-NetbiosName -Identity $Ident if ($NetbiosConversion.DomainName) { $TemporaryDomainName = $NetbiosConversion.DomainName $Ident = $NetbiosConversion.Name } } } } # Building up ADSI call $Search = [System.DirectoryServices.DirectorySearcher]::new() #$Search.SizeLimit = $SizeLimit 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)" } } #Convert Identity Input String to HEX, if possible Try { $IdentityGUID = "" ([System.Guid]$Ident).ToByteArray() | ForEach-Object { $IdentityGUID += $("\{0:x2}" -f $_) } } Catch { $IdentityGUID = "null" } # Building search filter $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', 'contact', 'inetOrgPerson', 'computer', 'user', 'foreignSecurityPrincipal', 'msDS-ManagedServiceAccount', 'msDS-GroupManagedServiceAccount' -and (-not $IncludeAllTypes)) { Write-Warning "Get-WinADObject - Unsupported object ($Ident) of type $ObjectClass. Only user,computer,group, foreignSecurityPrincipal, msDS-ManagedServiceAccount, msDS-GroupManagedServiceAccount are displayed by default. Use IncludeAllTypes switch to display all if nessecary." continue } $Members = $Object.properties.member -as [array] if ($ObjectClass -eq 'group') { # we only do this additional step when requested. It's not nessecary for day to day use but can hurt performance real bad for normal use cases # This was especially visible for group with 50k members and Get-WinADObjectMember which doesn't even require this data if ($IncludeGroupMembership) { # This is weird case but for some reason $Object.properties.member doesn't always return all values # the workaround is to do additional query for group and assing it $GroupMembers = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context, $Ident).Members try { $Members = [System.Collections.Generic.List[string]]::new() foreach ($Member in $Object.properties.member) { if ($Member) { $Members.Add($Member) } } foreach ($Member in $GroupMembers) { if ($Member.DistinguishedName) { if ($Member.DistinguishedName -notin $Members) { $Members.Add($Member.DistinguishedName) } } elseif ($Member.DisplayName) { $Members.Add($Member.DisplayName) } else { $Members.Add($Member.Sid) } } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw return } else { Write-Warning -Message "Error while parsing group members for $($Ident): $($_.Exception.Message)" } } } } $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 '') { # If object is foreignSecurityPrincipal (which shouldn't happen at this point) we need to set it to temporary name we # used before. Usually this is to fix 'NT AUTHORITY\INTERACTIVE' # I have no clue if there's better way to do it $DisplayName = $ResolvedIdentity.Name if ($DisplayName -like '*\*') { $NetbiosWithName = $DisplayName -split '\\' if ($NetbiosWithName.Count -eq 2) { #$NetbiosName = $NetbiosWithName[0] $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 -in 'group', 'contact') { $null } else { $UAC -notcontains 'ACCOUNTDISABLE' } PasswordNeverExpire = if ($ObjectClass -in 'group', 'contact') { $null } else { $UAC -contains 'DONT_EXPIRE_PASSWORD' } DomainName = $ObjectDomainName Distinguishedname = $Object.properties.distinguishedname -as [string] #Adspath = $Object.properties.adspath -as [string] WhenCreated = $Object.properties.whencreated -as [string] WhenChanged = $Object.properties.whenchanged -as [string] #Deleted = $Object.properties.isDeleted -as [string] #Recycled = $Object.properties.isRecycled -as [string] UserPrincipalName = $Object.properties.userprincipalname -as [string] ObjectSID = $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 #Administrative = if ($Object.properties.admincount -eq '1') { $true } else { $false } #Type = $ResolvedIdentity.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) { # This is purely to get special types $ResolvedIdentity = ConvertFrom-SID -SID $ReturnObject['ObjectSID'] } $ReturnObject['Type'] = $ResolvedIdentity.Type } if ($ReturnObject['Type'] -eq 'WellKnownAdministrative') { if (-not $TemporaryDomainName) { # This is so BUILTIN\Administrators would not report domain name that's always related to current one, while it could be someone expects it to be from different forest # this is to mainly address issues with Get-ADACL IdentityReference returning data that's hard to manage otherwise $ReturnObject['DomainName'] = '' } } <# $LastLogon = $Object.properties.lastlogon -as [string] if ($LastLogon) { $LastLogonDate = [datetime]::FromFileTime($LastLogon) } else { $LastLogonDate = $null } $AccountExpires = $Object.Properties.accountexpires -as [string] $AccountExpiresDate = ConvertTo-Date -accountExpires $AccountExpires $PasswordLastSet = $Object.Properties.pwdlastset -as [string] if ($PasswordLastSet) { $PasswordLastSetDate = [datetime]::FromFileTime($PasswordLastSet) } else { $PasswordLastSetDate = $null } $BadPasswordTime = $Object.Properties.badpasswordtime -as [string] if ($BadPasswordTime) { $BadPasswordDate = [datetime]::FromFileTime($BadPasswordTime) } else { $BadPasswordDate = $null } $ReturnObject['LastLogonDate'] = $LastLogonDate $ReturnObject['PasswordLastSet'] = $PasswordLastSetDate $ReturnObject['BadPasswordTime'] = $BadPasswordDate $ReturnObject['AccountExpiresDate'] = $AccountExpiresDate #> if ($Cache) { $Script:CacheObjectsWinADObject[$TemporaryName] = [PSCustomObject] $ReturnObject $Script:CacheObjectsWinADObject[$TemporaryName] } else { [PSCustomObject] $ReturnObject } } } } } function Get-WinADPasswordPolicy { <# .SYNOPSIS Get password policies from Active Directory include fine grained password policies .DESCRIPTION Get password policies from Active Directory include fine grained password policies Please keep in mind that reading fine grained password policies requires extended rights It's not available to standard users .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 NoSorting Do not sort output by Precedence .PARAMETER ReturnHashtable Return hashtable instead of array. Useful for internal processing such as Get-WinADUsers .EXAMPLE Get-WinADPasswordPolicy | Format-Table .NOTES General notes #> [CmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $NoSorting, [parameter(DontShow)][switch] $ReturnHashtable ) $FineGrainedPolicy = [ordered] @{} $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -ExcludeDomains $ExcludeDomains -IncludeDomains $IncludeDomains $AllPasswordPolicies = foreach ($Domain in $ForestInformation.Domains) { $Policies = @( Get-ADDefaultDomainPasswordPolicy -Server $ForestInformation['QueryServers'][$Domain].Hostname[0] Get-ADFineGrainedPasswordPolicy -Filter "*" -Server $ForestInformation['QueryServers'][$Domain].Hostname[0] ) foreach ($Policy in $Policies) { $FineGrainedPolicy[$Policy.DistinguishedName] = [PSCustomObject] @{ Name = if ($Policy.ObjectClass -contains 'domainDNS') { 'Default' } else { $Policy.Name } DomainName = $Domain Type = if ($Policy.ObjectClass -contains 'domainDNS') { 'Default Password Policy' } else { 'Fine Grained Password Policy' } Precedence = if ($Policy.Precedence) { $Policy.Precedence } else { 99999 } MinPasswordLength = $Policy.MinPasswordLength MaxPasswordAge = $Policy.MaxPasswordAge MinPasswordAge = $Policy.MinPasswordAge PasswordHistoryCount = $Policy.PasswordHistoryCount ComplexityEnabled = $Policy.ComplexityEnabled ReversibleEncryptionEnabled = $Policy.ReversibleEncryptionEnabled LockoutDuration = $Policy.LockoutDuration LockoutObservationWindow = $Policy.LockoutObservationWindow LockoutThreshold = $Policy.LockoutThreshold AppliesTo = $Policy.AppliesTo AppliesToCount = if ($Policy.AppliesTo) { $Policy.AppliesTo.Count } else { 0 } AppliesToName = if ($Policy.AppliesTo) { foreach ($DN in $Policy.AppliesTo) { ConvertFrom-DistinguishedName -DistinguishedName $DN -ToLastName } } else { $null } DistinguishedName = $Policy.DistinguishedName } if ($Policy.ObjectClass -contains 'domainDNS') { $FineGrainedPolicy[$Domain] = $FineGrainedPolicy[$Policy.DistinguishedName] $FineGrainedPolicy[$Domain] } else { $FineGrainedPolicy[$Policy.DistinguishedName] = $FineGrainedPolicy[$Policy.DistinguishedName] $FineGrainedPolicy[$Policy.DistinguishedName] } } } if ($ReturnHashtable) { $FineGrainedPolicy } else { if (-not $NoSorting) { $AllPasswordPolicies | Sort-Object -Property Precedence } else { $AllPasswordPolicies } } } Function Get-WinADPrivilegedObjects { [alias('Get-WinADPriviligedObjects')] [cmdletbinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $LegitimateOnly, [switch] $OrphanedOnly, #[switch] $Unique, [switch] $SummaryOnly, [switch] $DoNotShowCriticalSystemObjects, [alias('Display')][switch] $Formatted, [string] $Splitter = [System.Environment]::NewLine, [System.Collections.IDictionary] $ExtendedForestInformation ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $Domains = $ForestInformation.Domains $UsersWithAdminCount = foreach ($Domain in $Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] if ($DoNotShowCriticalSystemObjects) { $Objects = Get-ADObject -Filter 'admincount -eq 1 -and iscriticalsystemobject -notlike "*"' -Server $QueryServer -Properties whenchanged, whencreated, admincount, isCriticalSystemObject, samaccountname, "msDS-ReplAttributeMetaData" } else { $Objects = Get-ADObject -Filter 'admincount -eq 1' -Server $QueryServer -Properties whenchanged, whencreated, admincount, isCriticalSystemObject, samaccountname, "msDS-ReplAttributeMetaData" } foreach ($_ in $Objects) { [PSCustomObject] @{ Domain = $Domain distinguishedname = $_.distinguishedname whenchanged = $_.whenchanged whencreated = $_.whencreated admincount = $_.admincount SamAccountName = $_.SamAccountName objectclass = $_.objectclass isCriticalSystemObject = if ($_.isCriticalSystemObject) { $true } else { $false } adminCountDate = ($_.'msDS-ReplAttributeMetaData' | ForEach-Object { ([XML]$_.Replace("`0", "")).DS_REPL_ATTR_META_DATA | Where-Object { $_.pszAttributeName -eq "admincount" } }).ftimeLastOriginatingChange | Get-Date -Format MM/dd/yyyy } } } $CriticalGroups = foreach ($Domain in $Domains) { $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] Get-ADGroup -Filter 'admincount -eq 1 -and iscriticalsystemobject -eq $true' -Server $QueryServer #| Select-Object @{name = 'Domain'; expression = { $domain } }, distinguishedname } $CacheCritical = [ordered] @{} foreach ($Group in $CriticalGroups) { [Array] $Members = Get-WinADGroupMember -Identity $Group.distinguishedname -Verbose:$false -All Write-Verbose -Message "Processing $($Group.DistinguishedName) with $($Members.Count) members" foreach ($Member in $Members) { if ($null -ne $Member -and $Member.DistinguishedName) { if (-not $CacheCritical[$Member.DistinguishedName]) { $CacheCritical[$Member.DistinguishedName] = [System.Collections.Generic.List[string]]::new() } if ($Group.DistinguishedName -notin $CacheCritical[$Member.DistinguishedName]) { $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-WinADProtocol { <# .SYNOPSIS Gets current SCHANNEL settings for Windows Clients and Servers. .DESCRIPTION Gets current SCHANNEL settings for Windows Clients and Servers. By default scans all Domain Controllers in a forest .PARAMETER ComputerName Provides ability to query specific servers or computers. .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 .EXAMPLE An example .NOTES Based on: - https://stackoverflow.com/questions/51405489/what-is-the-difference-between-the-disabledbydefault-and-enabled-ssl-tls-registr - https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/manage-ssl-protocols-in-ad-fs - https://docs.microsoft.com/en-us/windows-server/security/tls/tls-registry-settings - https://docs.microsoft.com/en-us/security/engineering/solving-tls1-problem - https://docs.microsoft.com/en-us/windows/win32/secauthn/protocols-in-tls-ssl--schannel-ssp- #> [CmdletBinding()] param( [alias('Server')][string[]] $ComputerName, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [string[]] $ExcludeDomainControllers, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [System.Collections.IDictionary] $ExtendedForestInformation ) $Computers = @( if ($ComputerName) { foreach ($Computer in $ComputerName) { [PSCustomObject] @{ HostName = $Computer Domain = 'Not provided' } } } else { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation foreach ($DC in $ForestInformation.ForestDomainControllers) { [PSCustomObject] @{ HostName = $DC.HostName Domain = $DC.Domain } } } ) foreach ($DC in $Computers) { #$Connectivity = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL' $Version = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' if ($Version.PSConnection -eq $true) { $WindowsVersion = ConvertTo-OperatingSystem -OperatingSystem $Version.ProductName -OperatingSystemVersion $Version.CurrentBuildNumber # According to this https://github.com/MicrosoftDocs/windowsserverdocs/issues/2783 SCHANNEL service requires direct enablement $ProtocolDefaults = Get-ProtocolDefaults -WindowsVersion $WindowsVersion $Client = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Client' $Server = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server' $Client30 = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Client' $Server30 = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Server' $ClientTLS10 = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client' $ServerTLS10 = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server' $ClientTLS11 = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client' $ServerTLS11 = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server' $ClientTLS12 = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client' $ServerTLS12 = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server' #$ClientTLS13 = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client' #$ServerTLS13 = Get-PSRegistry -ComputerName $DC.HostName -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Server' [PSCustomObject] @{ ComputerName = $DC.HostName DomainName = $DC.Domain Version = $WindowsVersion SSL_2_Client = Get-ProtocolStatus -RegistryEntry $Client -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'SSL2Client' SSL_2_Server = Get-ProtocolStatus -RegistryEntry $Server -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'SSL2Server' SSL_3_Client = Get-ProtocolStatus -RegistryEntry $Client30 -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'SSL3Client' SSL_3_Server = Get-ProtocolStatus -RegistryEntry $Server30 -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'SSL3Server' TLS_10_Client = Get-ProtocolStatus -RegistryEntry $ClientTLS10 -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'TLS10Client' TLS_10_Server = Get-ProtocolStatus -RegistryEntry $ServerTLS10 -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'TLS10Server' TLS_11_Client = Get-ProtocolStatus -RegistryEntry $ClientTLS11 -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'TLS11Client' TLS_11_Server = Get-ProtocolStatus -RegistryEntry $ServerTLS11 -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'TLS11Server' TLS_12_Client = Get-ProtocolStatus -RegistryEntry $ClientTLS12 -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'TLS12Client' TLS_12_Server = Get-ProtocolStatus -RegistryEntry $ServerTLS12 -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'TLS12Server' TLS_13_Client = Get-ProtocolStatus -RegistryEntry $ClientTLS13 -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'TLS13Client' TLS_13_Server = Get-ProtocolStatus -RegistryEntry $ServerTLS13 -WindowsVersion $WindowsVersion -ProtocolDefaults $ProtocolDefaults -Protocol 'TLS13Server' } } else { [PSCustomObject] @{ ComputerName = $DC.HostName DomainName = $DC.Domain Version = 'Unknown' SSL_2_Client = 'No connection' SSL_2_Server = 'No connection' SSL_3_Client = 'No connection' SSL_3_Server = 'No connection' TLS_10_Client = 'No connection' TLS_10_Server = 'No connection' TLS_11_Client = 'No connection' TLS_11_Server = 'No connection' TLS_12_Client = 'No connection' TLS_12_Server = 'No connection' #TLS_13_Client = Get-ProtocolStatus -RegistryEntry $ClientTLS13 #TLS_13_Server = Get-ProtocolStatus -RegistryEntry $ServerTLS13 } } } } 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() # MailNickname = $ADUser.mailNickName } foreach ($Proxy in $ADUser.ProxyAddresses) { if ($Proxy -like '*,*') { # Most likely someone added proxy address with comma instead of each email address separatly $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 #-replace 'SMTP:', '' } if ($ToLower) { $Proxy = $Proxy.ToLower() } $Summary.x500.Add($Proxy) } elseif ($Proxy.StartsWith('sip:')) { if ($RemovePrefix) { $Proxy = $Proxy #-replace 'SMTP:', '' } if ($ToLower) { $Proxy = $Proxy.ToLower() } $Summary.Sip.Add($Proxy) } else { if ($RemovePrefix) { $Proxy = $Proxy #-replace 'SMTP:', '' } if ($ToLower) { $Proxy = $Proxy.ToLower() } $Summary.Other.Add($Proxy) } } if ($Formatted) { $Summary.Primary = $Summary.Primary -join $Splitter $Summary.Secondary = $Summary.Secondary -join $Splitter $Summary.Sip = $Summary.Sip -join $Splitter $Summary.x500 = $Summary.x500 -join $Splitter $Summary.Other = $Summary.Other -join $Splitter } $Summary } function Get-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' #,'PrimaryGroup', 'PrimaryGroupID', 'AccountExpirationDate', 'AccountNotDelegated', #'AllowReversiblePasswordEncryption', 'CannotChangePassword', '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' #'msDS-ManagedPasswordId', 'msDS-ManagedPasswordPreviousId' ) $Accounts = Get-ADServiceAccount -Filter "*" -Server $QueryServer -Properties $Properties $Output[$Domain] = foreach ($Account in $Accounts) { #$Account 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 # : True # : WO_SVC_Delete$ ObjectClass = $Account.ObjectClass # : msDS-ManagedServiceAccount CanonicalName = $Account.CanonicalName # : ad.evotec.xyz/Managed Service Accounts/WO_SVC_Delete DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Account.DistinguishedName Description = $Account.Description PasswordLastChangedDays = $PasswordLastChangedDays LastLogonDays = $LastLogonDays 'ManagedPasswordIntervalInDays' = $Account.'ManagedPasswordIntervalInDays' 'msDS-AllowedToDelegateTo' = $Account.'msDS-AllowedToDelegateTo' # : {CN=EVOWIN,OU=Computers,OU=Devices,OU=Production,DC=ad,DC=evotec,DC=xyz} 'msDS-HostServiceAccountBL' = $Account.'msDS-HostServiceAccountBL' # : {CN=EVOWIN,OU=Computers,OU=Devices,OU=Production,DC=ad,DC=evotec,DC=xyz} 'msDS-AuthenticatedAtDC' = $Account.'msDS-AuthenticatedAtDC' 'msDS-AllowedToActOnBehalfOfOtherIdentity' = $Account.'msDS-AllowedToActOnBehalfOfOtherIdentity' 'PrincipalsAllowedToRetrieveManagedPassword' = $Account.'PrincipalsAllowedToRetrieveManagedPassword' 'PrincipalsAllowedToDelegateToAccount' = $Account.'PrincipalsAllowedToDelegateToAccount' #'msDS-ManagedPasswordId' = $Account.'msDS-ManagedPasswordId' 'msDS-GroupMSAMembershipAccess' = $Account.'msDS-GroupMSAMembership'.Access.IdentityReference.Value 'msDS-GroupMSAMembershipOwner' = $Account.'msDS-GroupMSAMembership'.Owner #'msDS-ManagedPasswordPreviousId' = $Account.'msDS-ManagedPasswordPreviousId' 'msDS-RevealedDSAs' = $Account.'msDS-RevealedDSAs' 'servicePrincipalName' = $Account.servicePrincipalName AccountNotDelegated = $Account.AccountNotDelegated # : False TrustedForDelegation = $Account.TrustedForDelegation # : False TrustedToAuthForDelegation = $Account.TrustedToAuthForDelegation # : False AccountExpirationDate = $Account.AccountExpirationDate #AllowReversiblePasswordEncryption = $Account.AllowReversiblePasswordEncryption # : False #CannotChangePassword = $Account.CannotChangePassword # : False #'msDS-SupportedEncryptionTypes' = $Account.'msDS-SupportedEncryptionTypes' # : 28 msDSSupportedEncryptionTypes = Get-ADEncryptionTypes -Value $Account.'msds-supportedencryptiontypes' # 'msDS-User-Account-Control-Computed' = $Account.'msDS-User-Account-Control-Computed' # : 0 #ObjectGUID = $Account.ObjectGUID # : 573ff95e-c1f8-45e2-9b64-662fb9cb0615 PasswordNeverExpires = $Account.PasswordNeverExpires # : False PasswordNotRequired = $Account.PasswordNotRequired # : False #PrimaryGroup = $Account.PrimaryGroup # : CN=Domain Computers,CN=Users,DC=ad,DC=evotec,DC=xyz #PrimaryGroupID = $Account.PrimaryGroupID # : 515 #SID = $Account.SID # : S-1-5-21-853615985-2870445339-3163598659-4607 #UserPrincipalName = $Account.UserPrincipalName # : LastLogonDate = $Account.LastLogonDate # : PasswordLastSet = $Account.PasswordLastSet # : 15.04.2021 22:47:40 WhenChanged = $Account.WhenChanged # : 15.04.2021 22:47:40 WhenCreated = $Account.WhenCreated # : 15.04.2021 22:47:40 SamAccountName = $Account.SamAccountName DistinguishedName = $Account.DistinguishedName # : CN=WO_SVC_Delete,CN=Managed Service Accounts,DC=ad,DC=evotec,DC=xyz 'msDS-GroupMSAMembership' = $Account.'msDS-GroupMSAMembership' # 'msDS-ManagedPasswordInterval' = $Account.'msDS-ManagedPasswordInterval' } } } 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] @{ <# Regex extracts AD1 and AD2 CN=d1695d10-8d24-41db-bb0f-2963e2c7dfcd,CN=NTDS Settings,CN=AD1,CN=Servers,CN=KATOWICE-1,CN=Sites,CN=Configuration,DC=ad,DC=evotec,DC=xyz CN=NTDS Settings,CN=AD2,CN=Servers,CN=KATOWICE-1,CN=Sites,CN=Configuration,DC=ad,DC=evotec,DC=xyz #> '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 } <# Regex extracts KATOWICE-1 CN=d1695d10-8d24-41db-bb0f-2963e2c7dfcd,CN=NTDS Settings,CN=AD1,CN=Servers,CN=KATOWICE-1,CN=Sites,CN=Configuration,DC=ad,DC=evotec,DC=xyz CN=NTDS Settings,CN=AD2,CN=Servers,CN=KATOWICE-1,CN=Sites,CN=Configuration,DC=ad,DC=evotec,DC=xyz #> '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 } #'Options' = $_.Options 'When Created' = $_.WhenCreated 'When Changed' = $_.WhenChanged 'Is Deleted' = $_.IsDeleted } } else { $Dictionary = [PSCustomObject] @{ <# Regex extracts AD1 and AD2 CN=d1695d10-8d24-41db-bb0f-2963e2c7dfcd,CN=NTDS Settings,CN=AD1,CN=Servers,CN=KATOWICE-1,CN=Sites,CN=Configuration,DC=ad,DC=evotec,DC=xyz CN=NTDS Settings,CN=AD2,CN=Servers,CN=KATOWICE-1,CN=Sites,CN=Configuration,DC=ad,DC=evotec,DC=xyz #> 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 } <# Regex extracts KATOWICE-1 CN=d1695d10-8d24-41db-bb0f-2963e2c7dfcd,CN=NTDS Settings,CN=AD1,CN=Servers,CN=KATOWICE-1,CN=Sites,CN=Configuration,DC=ad,DC=evotec,DC=xyz CN=NTDS Settings,CN=AD2,CN=Servers,CN=KATOWICE-1,CN=Sites,CN=Configuration,DC=ad,DC=evotec,DC=xyz #> 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 } #Options = $_.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 } #ReplInterval : 15 Created = $_.WhenCreated Modified = $_.WhenChanged #Deleted : #InterSiteTransportProtocol : IP 'Protected From Accidental Deletion' = $_.ProtectedFromAccidentalDeletion } } else { [PSCustomObject] @{ Name = $_.CN Cost = $_.Cost ReplicationFrequencyInMinutes = $_.ReplInterval Options = if ($Splitter -ne '') { $Options -Join $Splitter } else { $Options } #ReplInterval : 15 Created = $_.WhenCreated Modified = $_.WhenChanged #Deleted : #InterSiteTransportProtocol : IP ProtectedFromAccidentalDeletion = $_.ProtectedFromAccidentalDeletion } } } } Function Get-WinADSiteOptions { [CmdletBinding()] Param( ) [Flags()] enum nTDSSiteSettingsFlags { NTDSSETTINGS_OPT_IS_AUTO_TOPOLOGY_DISABLED = 0x1 NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED = 0x2 NTDSSETTINGS_OPT_IS_TOPL_MIN_HOPS_DISABLED = 0x4 NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED = 0x8 NTDSSETTINGS_OPT_IS_INTER_SITE_AUTO_TOPOLOGY_DISABLED = 0x10 NTDSSETTINGS_OPT_IS_GROUP_CACHING_ENABLED = 0x20 NTDSSETTINGS_OPT_FORCE_KCC_WHISTLER_BEHAVIOR = 0x40 NTDSSETTINGS_OPT_FORCE_KCC_W2K_ELECTION = 0x80 NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED = 0x100 NTDSSETTINGS_OPT_IS_SCHEDULE_HASHING_ENABLED = 0x200 NTDSSETTINGS_OPT_IS_REDUNDANT_SERVER_TOPOLOGY_ENABLED = 0x400 NTDSSETTINGS_OPT_W2K3_IGNORE_SCHEDULES = 0x800 NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x1000 } $RootDSE = Get-ADRootDSE $Sites = Get-ADObject -Filter 'objectClass -eq "site"' -SearchBase $RootDSE.ConfigurationNamingContext foreach ($Site In $Sites) { $SiteSettings = Get-ADObject "CN=NTDS Site Settings,$($Site.DistinguishedName)" -Properties Options If (-not $SiteSettings.PSObject.Properties.Match('Options').Count -OR $SiteSettings.Options -EQ 0) { [PSCustomObject]@{ SiteName = $Site.Name DistinguishedName = $Site.DistinguishedName SiteOptions = '(none)' } } Else { [PSCustomObject]@{ SiteName = $Site.Name; DistinguishedName = $Site.DistinguishedName; Options = $SiteSettings.Options SiteOptions = [nTDSSiteSettingsFlags] $SiteSettings.Options } } } } function Get-WinADTomebstoneLifetime { [Alias('Get-WinADForestTomebstoneLifetime')] [CmdletBinding()] param( [alias('ForestName')][string] $Forest, [System.Collections.IDictionary] $ExtendedForestInformation ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation # Check tombstone lifetime (if blank value is 60) # Recommended value 720 # Minimum value 180 $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 -or $null -eq $Output.tombstoneLifetime) { [PSCustomObject] @{ TombstoneLifeTime = 60 } } else { [PSCustomObject] @{ TombstoneLifeTime = $Output.tombstoneLifetime } } } function Get-WinADTrust { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER Recursive Parameter description .PARAMETER Nesting Parameter description .PARAMETER UniqueTrusts Parameter description .EXAMPLE An example .NOTES General notes #> [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 # https://github.com/vletoux/pingcastle/issues/9 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 { # Assuming all patches are installed (past July 2019) $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 #$Domain 'TrustTarget' = $Trust.Details.TargetName #$Trust.Target 'TrustDirection' = $Trust.Details.TrustDirection.ToString() #$Trust.Direction.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" #'SIDFilteringForestAware' = $null '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" #'IsTreeParent' = $null #$Trust.IsTreeParent #'IsTreeRoot' = $Trust.Details.TrustType.ToString() -eq 'TreeRoot' 'IsTGTDelegationEnabled' = $TGTDelegation #'TrustedPolicy' = $null #$Trust.TrustedPolicy #'TrustingPolicy' = $null #$Trust.TrustingPolicy '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 { <# .SYNOPSIS Short description .DESCRIPTION Long description .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 Display Parameter description .PARAMETER ExtendedForestInformation Parameter description .PARAMETER Unique Parameter description .EXAMPLE An example .NOTES General notes #> [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', # TrustIsOk/TrustStatus are covered by this '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 { <# .SYNOPSIS Get-WinADUsers is a function that retrieves all users from Active Directory. It can be used to retrieve users from a single domain or from all domains in the forest. .DESCRIPTION Get-WinADUsers is a function that retrieves all users from Active Directory. It can be used to retrieve users from a single domain or from all domains in the 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 PerDomain Return results per domain .PARAMETER AddOwner Add Owner information to the output .EXAMPLE An example .NOTES General notes #> [cmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $PerDomain, [switch] $AddOwner ) $AllUsers = [ordered] @{} $AllContacts = [ordered] @{} $AllGroups = [ordered] @{} $CacheUsersReport = [ordered] @{} $Today = Get-Date $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation $ErrorCount = 0 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' 'nTSecurityDescriptor', 'Country', 'Title', 'Department' 'msds-resultantpso' ) try { $AllUsers[$Domain] = Get-ADUser -Filter "*" -Properties $Properties -Server $QueryServer #$ForestInformation['QueryServers'][$Domain].HostName[0] } catch { $ErrorCount++ Write-Warning -Message "Get-WinADUsers - Failed to get users from $Domain using $($QueryServer). Error: $($_.Exception.Message)" } try { $AllContacts[$Domain] = Get-ADObject -Filter 'objectClass -eq "contact"' -Properties SamAccountName, Mail, Name, DistinguishedName, WhenChanged, Whencreated, DisplayName -Server $QueryServer } catch { $ErrorCount++ Write-Warning -Message "Get-WinADUsers - Failed to get contacts from $Domain using $($QueryServer). Error: $($_.Exception.Message)" } $Properties = @( 'SamAccountName', 'CanonicalName', 'Mail', 'Name', 'DistinguishedName', 'isCriticalSystemObject', 'ObjectSID' ) try { $AllGroups[$Domain] = Get-ADGroup -Filter "*" -Properties $Properties -Server $QueryServer } catch { $ErrorCount++ Write-Warning -Message "Get-WinADUsers - Failed to get groups from $Domain using $($QueryServer). Error: $($_.Exception.Message)" } } if ($ErrorCount -gt 0) { Write-Warning -Message "Get-WinADUsers - Failed to get data from domains. Found $ErrorCount errors. Please check the error messages above." return } foreach ($Domain in $AllUsers.Keys) { foreach ($U in $AllUsers[$Domain]) { $CacheUsersReport[$U.DistinguishedName] = $U } } foreach ($Domain in $AllContacts.Keys) { foreach ($C in $AllContacts[$Domain]) { $CacheUsersReport[$C.DistinguishedName] = $C } } foreach ($Domain in $AllGroups.Keys) { foreach ($G in $AllGroups[$Domain]) { $CacheUsersReport[$G.DistinguishedName] = $G } } $PasswordPolicies = Get-WinADPasswordPolicy -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ReturnHashtable $Output = [ordered] @{} foreach ($Domain in $ForestInformation.Domains) { $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 = $CacheUsersReport[$User.Manager].Name $ManagerSamAccountName = $CacheUsersReport[$User.Manager].SamAccountName $ManagerEmail = $CacheUsersReport[$User.Manager].Mail $ManagerEnabled = $CacheUsersReport[$User.Manager].Enabled $ManagerLastLogon = $CacheUsersReport[$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) { # This is standard situation where users password is expiring as needed 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 { # This is non-standard situation. This basically means most likely Fine Grained Group Policy is in action where it makes PasswordNeverExpires $true # Since FGP policies are a bit special they do not tick the PasswordNeverExpires box, but at the same time value for "msDS-UserPasswordExpiryTimeComputed" is set to 9223372036854775807 $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 if ($User.'msds-resultantpso') { # $PasswordPolicy = 'FineGrained' if ($PasswordPolicies[$User.'msds-resultantpso']) { $PasswordPolicyName = $PasswordPolicies[$User.'msds-resultantpso'].Name $PasswordPolicyLength = $PasswordPolicies[$User.'msds-resultantpso'].MinPasswordLength } else { $PasswordPolicyName = ConvertFrom-DistinguishedName -DistinguishedName $User.'msds-resultantpso' $PasswordPolicyLength = 'No permission' } } else { # $PasswordPolicy = 'Default' $PasswordPolicyName = 'Default Password Policy' $PasswordPolicyLength = $PasswordPolicies[$Domain].MinPasswordLength } if ($AddOwner) { $Owner = Get-ADACLOwner -ADObject $User -Verbose -Resolve [PSCustomObject] @{ Name = $User.Name SamAccountName = $User.SamAccountName Domain = $Domain WhenChanged = $User.WhenChanged Enabled = $User.Enabled ObjectClass = $User.ObjectClass #IsMissing = if ($Group) { $false } else { $true } HasMailbox = $HasMailbox MustChangePasswordAtLogon = if ($User.pwdLastSet -eq 0 -and $User.PasswordExpired -eq $true) { $true } else { $false } #PasswordPolicy = $PasswordPolicy PasswordPolicyName = $PasswordPolicyName PasswordPolicyMinLength = $PasswordPolicyLength PasswordNeverExpires = $PasswordNeverExpires PasswordNotRequired = $User.PasswordNotRequired LastLogonDays = $LastLogonDays PasswordLastDays = $PasswordLastDays DaysToExpire = $DaysToExpire ManagerStatus = $ManagerStatus Manager = $Manager ManagerSamAccountName = $ManagerSamAccountName ManagerEmail = $ManagerEmail ManagerLastLogonDays = $ManagerLastLogonDays OwnerName = $Owner.OwnerName OwnerSID = $Owner.OwnerSID OwnerType = $Owner.OwnerType Level0 = $Region Level1 = $Country Title = $User.'Title' Department = $User.'Department' Country = Convert-CountryCodeToCountry -CountryCode $User.Country DistinguishedName = $User.DistinguishedName LastLogonDate = $User.LastLogonDate PasswordLastSet = $User.PasswordLastSet PasswordExpiresOn = $DateExpiry PasswordExpired = $User.PasswordExpired CannotChangePassword = $User.CannotChangePassword TrustedForDelegation = $User.TrustedForDelegation ManagerDN = $User.Manager ManagerLastLogon = $ManagerLastLogon Group = $Group Description = $User.Description UserPrincipalName = $User.UserPrincipalName RecipientTypeDetails = $msExchRecipientTypeDetails RecipientDisplayType = $msExchRecipientDisplayType RemoteRecipientType = $msExchRemoteRecipientType WhenCreated = $User.WhenCreated } } else { [PSCustomObject] @{ Name = $User.Name SamAccountName = $User.SamAccountName Domain = $Domain WhenChanged = $User.WhenChanged Enabled = $User.Enabled ObjectClass = $User.ObjectClass #IsMissing = if ($Group) { $false } else { $true } HasMailbox = $HasMailbox MustChangePasswordAtLogon = if ($User.pwdLastSet -eq 0 -and $User.PasswordExpired -eq $true) { $true } else { $false } #PasswordPolicy = $PasswordPolicy PasswordPolicyName = $PasswordPolicyName PasswordPolicyMinLength = $PasswordPolicyLength 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 Title = $User.'Title' Department = $User.'Department' Country = Convert-CountryCodeToCountry -CountryCode $User.Country DistinguishedName = $User.DistinguishedName LastLogonDate = $User.LastLogonDate PasswordLastSet = $User.PasswordLastSet PasswordExpiresOn = $DateExpiry PasswordExpired = $User.PasswordExpired CannotChangePassword = $User.CannotChangePassword TrustedForDelegation = $User.TrustedForDelegation 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.$_ } <# $DomainDistinguishedName = $DomainInformation.DistinguishedName $DefaultWellKnownFolders = [ordered] @{ UsersContainer = "CN=Users,$DomainDistinguishedName" ComputersContainer = "CN=Computers,$DomainDistinguishedName" DomainControllersContainer = "OU=Domain Controllers,$DomainDistinguishedName" DeletedObjectsContainer = "CN=Deleted Objects,$DomainDistinguishedName" SystemsContainer = "CN=System,$DomainDistinguishedName" LostAndFoundContainer = "CN=LostAndFound,$DomainDistinguishedName" QuotasContainer = "CN=NTDS Quotas,$DomainDistinguishedName" ForeignSecurityPrincipalsContainer = "CN=ForeignSecurityPrincipals,$DomainDistinguishedName" } #> #Compare-MultipleObjects -Object @($DefaultWellKnownFolders, $CurrentWellKnownFolders) -SkipProperties if ($AsHashtable) { $CurrentWellKnownFolders } else { [PSCustomObject] $CurrentWellKnownFolders } } } #Get-WinADWellKnownFolders -IncludeDomains 'ad.evotec.xyz' 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, [switch] $SplitReports ) 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 # Verify requested types are supported $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 # Lets make sure we only enable those types which are requestd by user if ($Type) { foreach ($T in $Script:ADEssentialsConfiguration.Keys) { $Script:ADEssentialsConfiguration[$T].Enabled = $false } # Lets enable all requested ones foreach ($T in $Type) { $Script:ADEssentialsConfiguration[$T].Enabled = $true } } # Build data 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]) { # in some cases the return will be wrapped in Hashtable/orderedDictionary and we need to handle this without an array $Script:Reporting[$T]['Data'] = $OutputCommand } else { # since sometimes it can be 0 or 1 objects being returned we force it being an array $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 if ($SplitReports) { Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report for ', $T -Color Yellow, DarkGray, Yellow $TimeLogHTML = Start-TimeLog New-HTMLReportADEssentialsWithSplit -FilePath $FilePath -Online:$Online -HideHTML:$HideHTML -CurrentReport $T $TimeLogEndHTML = Stop-TimeLog -Time $TimeLogHTML -Option OneLiner Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report for', $T, " [Time to execute: $TimeLogEndHTML]" -Color Yellow, DarkGray, Yellow, DarkGray } } } if ( -not $SplitReports) { Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report' -Color Yellow, DarkGray, Yellow $TimeLogHTML = Start-TimeLog if (-not $FilePath) { $FilePath = Get-FileName -Extension 'html' -Temporary } New-HTMLReportADEssentials -Type $Type -Online:$Online.IsPresent -HideHTML:$HideHTML.IsPresent -FilePath $FilePath $TimeLogEndHTML = Stop-TimeLog -Time $TimeLogHTML -Option OneLiner Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report', " [Time to execute: $TimeLogEndHTML]" -Color Yellow, DarkGray, Yellow, DarkGray } Reset-ADEssentialsStatus if ($PassThru) { $Script:Reporting } } [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 New-ADACLObject { <# .SYNOPSIS Define ACL permissions to be applied during Set-ADACLObject and in DelegationModel PowerShell Module .DESCRIPTION Define ACL permissions to be applied during Set-ADACLObject and in DelegationModel PowerShell Module .PARAMETER Principal Principal to apply permissions to .PARAMETER SimplifiedDelegation An experimental parameter that allows to choose predefined set of permissions instead of defining multiple rules to cover a single instance. .PARAMETER AccessRule Access rule to apply. Choices are: - AccessSystemSecurity - 16777216 - The right to get or set the SACL in the object security descriptor. - CreateChild - 1 - The right to create children of the object. - Delete - 65536 - The right to delete the object. - DeleteChild - 2 - The right to delete children of the object. - DeleteTree - 64 - The right to delete all children of this object, regardless of the permissions of the children. - ExtendedRight - 256 A customized control access right. For a list of possible extended rights, see the Extended Rights article. For more information about extended rights, see the Control Access Rights article. - GenericAll - 983551 The right to create or delete children, delete a subtree, read and write properties, examine children and the object itself, add and remove the object from the directory, and read or write with an extended right. - GenericExecute - 131076 The right to read permissions on, and list the contents of, a container object. - GenericRead - 131220 The right to read permissions on this object, read all the properties on this object, list this object name when the parent container is listed, and list the contents of this object if it is a container. - GenericWrite - 131112 The right to read permissions on this object, write all the properties on this object, and perform all validated writes to this object. - ListChildren - 4 The right to list children of this object. For more information about this right, see the Controlling Object Visibility article. - ListObject -128 - The right to list a particular object. For more information about this right, see the Controlling Object Visibility article. - ReadControl - 131072 - The right to read data from the security descriptor of the object, not including the data in the SACL. - ReadProperty - 16 - The right to read properties of the object. - Self -8 - The right to perform an operation that is controlled by a validated write access right. - Synchronize -1048576 - The right to use the object for synchronization. This right enables a thread to wait until that object is in the signaled state. - WriteDacl - 262144 - The right to modify the DACL in the object security descriptor. - WriteOwner - 524288 - The right to assume ownership of the object. The user must be an object trustee. The user cannot transfer the ownership to other users. - WriteProperty -32 - The right to write properties of the object .PARAMETER AccessControlType Access control type to apply. Choices are: - Allow - 0 - The access control entry (ACE) allows the specified access. - Deny - 1 - The ACE denies the specified access. .PARAMETER ObjectType A list of schema properties to choose from. .PARAMETER InheritedObjectType A list of schema properties to choose from. .PARAMETER InheritanceType Inheritance type to apply. Choices are: - All - 3 - The ACE applies to the object and all its children. - Descendents - 2 - The ACE applies to the object and its immediate children. - SelfAndChildren - 1 - The ACE applies to the object and its immediate children. - None - 0 - The ACE applies only to the object. .PARAMETER OneLiner Return permissions as one liner. If used with Simplified Delegation multiple objects could be retured. .PARAMETER Force Forces refresh of the cache for user/groups. It's useful to run as a first query, especially if one created groups just before running the function .EXAMPLE New-ADACLObject -Principal 'przemyslaw.klys' -AccessControlType Allow -ObjectType All -InheritedObjectTypeName 'All' -AccessRule GenericAll -InheritanceType None .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'Standard')] param( [parameter(Mandatory, ParameterSetName = 'Simplified')] [parameter(Mandatory, ParameterSetName = 'Standard')][string] $Principal, [parameter(Mandatory, ParameterSetName = 'Simplified')] [string] $SimplifiedDelegation, [parameter(Mandatory, ParameterSetName = 'Standard')][alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights] $AccessRule, [parameter(Mandatory, ParameterSetName = 'Simplified')] [parameter(Mandatory, ParameterSetName = 'Standard')][System.Security.AccessControl.AccessControlType] $AccessControlType, [parameter(Mandatory, ParameterSetName = 'Standard')][alias('ObjectTypeName')][string] $ObjectType, [parameter(Mandatory, ParameterSetName = 'Standard')][alias('InheritedObjectTypeName')][string] $InheritedObjectType, [parameter(Mandatory, ParameterSetName = 'Simplified')] [parameter(Mandatory, ParameterSetName = 'Standard')][alias('ActiveDirectorySecurityInheritance')][nullable[System.DirectoryServices.ActiveDirectorySecurityInheritance]] $InheritanceType, [parameter(ParameterSetName = 'Simplified')] [parameter(ParameterSetName = 'Standard')][switch] $OneLiner, [parameter(ParameterSetName = 'Simplified')] [parameter(ParameterSetName = 'Standard')][switch] $Force ) $ConvertedIdentity = Convert-Identity -Identity $Principal -Verbose:$false -Force:$Force.IsPresent if ($ConvertedIdentity.Error) { Write-Warning -Message "New-ADACLObject - Converting identity $($Principal) failed with $($ConvertedIdentity.Error). Be warned." } $ConvertedPrincipal = ($ConvertedIdentity).Name if ($SimplifiedDelegation) { ConvertFrom-SimplifiedDelegation -Principal $ConvertedPrincipal -SimplifiedDelegation $SimplifiedDelegation -OneLiner:$OneLiner.IsPresent -AccessControlType $AccessControlType -InheritanceType $InheritanceType } else { ConvertTo-Delegation -AccessControlType $AccessControlType -InheritanceType $InheritanceType -Principal $ConvertedPrincipal -AccessRule $AccessRule -ObjectType $ObjectType -InheritedObjectType $InheritedObjectType -OneLiner:$OneLiner.IsPresent } } [scriptblock] $ADACLObjectAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) if (-not $Script:ADSchemaGuids) { Import-Module ActiveDirectory -Verbose:$false $Script:ADSchemaGuids = Convert-ADSchemaToGuid } $Script:ADSchemaGuids.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" } } Register-ArgumentCompleter -CommandName New-ADACLObject -ParameterName ObjectType -ScriptBlock $ADACLObjectAutoCompleter Register-ArgumentCompleter -CommandName New-ADACLObject -ParameterName InheritedObjectType -ScriptBlock $ADACLObjectAutoCompleter [scriptblock] $ADACLSimplifiedDelegationDefinition = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $Script:SimplifiedDelegationDefinitionList | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" } } Register-ArgumentCompleter -CommandName New-ADACLObject -ParameterName SimplifiedDelegation -ScriptBlock $ADACLSimplifiedDelegationDefinition function New-ADSite { [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true)][string]$Site, [Parameter(Mandatory = $true)][string]$Description, [Parameter(Mandatory = $true)][ValidateScript( { Get-ADReplicationSite -Identity $_ })][string]$SitePartner, [Parameter(Mandatory = $true)][array]$DefaultSite, [Parameter(Mandatory = $false)][array]$Subnets, [Parameter(Mandatory = $false)][System.Management.Automation.PSCredential]$Credential ) begin { $InformationPreference = "Continue" [string]$sServer = (Get-ADDomainController -Writable -Discover).HostName $Site = $Site.ToUpper() $SitePartner = $SitePartner.ToUpper() $sSiteLink = "$($Site)-$($SitePartner)" $sSiteLinkDescr = "$($SitePartner)-$($Site)" $aSiteLinkSites = @($Site, $SitePartner) } process { #region Create site try { $hParams = @{ Name = $Site Description = $Description Server = $sServer } if ($Credential) { $hParams.Credential = $Credential } New-ADReplicationSite @hParams Write-Verbose -Message "New-ADSite - Site $($Site) created" } catch { $ErrorMessage = $PSItem.Exception.Message Write-Warning -Message "New-ADSite - Error: $ErrorMessage" } #endregion #region Create/reconnect subnets try { if ($Subnets) { foreach ($subnet in $Subnets) { if (Get-ADReplicationSubnet -Filter "Name -eq '$subnet'") { Write-Warning -Message "$($subnet) exists, will try reconnect to new site" $hParams = @{ Identity = $subnet Site = $Site Description = $Description Server = $sServer } if ($Credential) { $hParams.Credential = $Credential } Set-ADReplicationSubnet @hParams Write-Verbose -Message "New-ADSite - Subnet $($subnet) reconnected" } else { $hParams = @{ Name = $subnet Site = $Site Description = $Description Server = $sServer } if ($Credential) { $hParams.Credential = $Credential } New-ADReplicationSubnet @hParams Write-Verbose -Message "New-ADSite - Subnet $($subnet) created" } } } } catch { $ErrorMessage = $PSItem.Exception.Message Write-Warning -Message "New-ADSite - Error: $ErrorMessage" } #endregion #region Create sitelink try { $hParams = @{ Name = $sSiteLink Description = $sSiteLinkDescr ReplicationFrequencyInMinutes = 15 Cost = 10 SitesIncluded = $aSiteLinkSites Server = $sServer } if ($Credential) { $hParams.Credential = $Credential } New-ADReplicationSiteLink @hParams Write-Verbose -Message "New-ADSite - $($sSiteLink) site link created" } catch { $ErrorMessage = $PSItem.Exception.Message Write-Warning -Message "New-ADSite - Error: $ErrorMessage" } #endregion #region Attach site to default sitelink try { $hParams = @{ Identity = $DefaultSite SitesIncluded = @{ Add = $Site } Server = $sServer } if ($Credential) { $hParams.Credential = $Credential } Set-ADReplicationSiteLink @hParams Write-Verbose -Message "New-ADSite - $($Site) added to $($DefaultSite)" } catch { $ErrorMessage = $PSItem.Exception.Message Write-Warning -Message "New-ADSite - Error: $ErrorMessage" } #endregion } end { } } function Remove-ADACL { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER ADObject Parameter description .PARAMETER ACL Parameter description .PARAMETER Principal Parameter description .PARAMETER AccessRule Parameter description .PARAMETER AccessControlType Parameter description .PARAMETER IncludeObjectTypeName Parameter description .PARAMETER IncludeInheritedObjectTypeName Parameter description .PARAMETER InheritanceType Parameter description .PARAMETER Force Breaks inheritance on the ACL when the rule has IsInherited set to $true. By default it will skip inherited rules .EXAMPLE An example .NOTES General notes #> [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ADObject')] param( [parameter(ParameterSetName = 'ADObject')][alias('Identity')][Array] $ADObject, [parameter(ParameterSetName = 'NTSecurityDescriptor', Mandatory)] [parameter(ParameterSetName = 'ACL', Mandatory)] [Array] $ACL, [parameter(ParameterSetName = 'ACL', Mandatory)] [parameter(ParameterSetName = 'ADObject')] [string] $Principal, [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [Alias('ActiveDirectoryRights')][System.DirectoryServices.ActiveDirectoryRights] $AccessRule, [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [System.Security.AccessControl.AccessControlType] $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow, [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [Alias('ObjectTypeName', 'ObjectType')][string[]] $IncludeObjectTypeName, [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [Alias('InheritedObjectTypeName', 'InheritedObjectType')][string[]] $IncludeInheritedObjectTypeName, [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [alias('ActiveDirectorySecurityInheritance')][nullable[System.DirectoryServices.ActiveDirectorySecurityInheritance]] $InheritanceType, [parameter(ParameterSetName = 'NTSecurityDescriptor')] [parameter(ParameterSetName = 'ACL')] [parameter(ParameterSetName = 'ADObject')] [switch] $Force, [parameter(ParameterSetName = 'NTSecurityDescriptor', Mandatory)] [alias('ActiveDirectorySecurity')][System.DirectoryServices.ActiveDirectorySecurity] $NTSecurityDescriptor ) if (-not $Script:ForestDetails) { Write-Verbose "Remove-ADACL - Gathering Forest Details" $Script:ForestDetails = Get-WinADForestDetails } if ($PSBoundParameters.ContainsKey('ADObject')) { foreach ($Object in $ADObject) { $getADACLSplat = @{ ADObject = $Object Bundle = $true Resolve = $true IncludeActiveDirectoryRights = $AccessRule Principal = $Principal AccessControlType = $AccessControlType IncludeObjectTypeName = $IncludeObjectTypeName IncludeActiveDirectorySecurityInheritance = $InheritanceType IncludeInheritedObjectTypeName = $IncludeInheritedObjectTypeName } Remove-EmptyValue -Hashtable $getADACLSplat $MYACL = Get-ADACL @getADACLSplat $removePrivateACLSplat = @{ ACL = $MYACL WhatIf = $WhatIfPreference Force = $Force.IsPresent } Remove-EmptyValue -Hashtable $removePrivateACLSplat Remove-PrivateACL @removePrivateACLSplat } } elseif ($PSBoundParameters.ContainsKey('ACL') -and $PSBoundParameters.ContainsKey('ntSecurityDescriptor')) { foreach ($SubACL in $ACL) { $removePrivateACLSplat = @{ ntSecurityDescriptor = $ntSecurityDescriptor ACL = $SubACL WhatIf = $WhatIfPreference Force = $Force.IsPresent } Remove-EmptyValue -Hashtable $removePrivateACLSplat Remove-PrivateACL @removePrivateACLSplat } } elseif ($PSBoundParameters.ContainsKey('ACL')) { foreach ($SubACL in $ACL) { $removePrivateACLSplat = @{ ACL = $SubACL Principal = $Principal AccessRule = $AccessRule AccessControlType = $AccessControlType IncludeObjectTypeName = $IncludeObjectTypeName IncludeInheritedObjectTypeName = $IncludeInheritedObjectTypeName InheritanceType = $InheritanceType WhatIf = $WhatIfPreference Force = $Force.IsPresent } Remove-EmptyValue -Hashtable $removePrivateACLSplat Remove-PrivateACL @removePrivateACLSplat } } } function Remove-WinADDFSTopology { <# .SYNOPSIS This command removes DFS topology objects from Active Directory that are missing one or more properties .DESCRIPTION This command removes DFS topology objects from Active Directory that are missing one or more properties. .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 Type Type of objects to remove - to remove those missing at least one property or all properties (MissingAtLeastOne, MissingAll) .EXAMPLE Remove-WinADDFSTopology -Type MissingAll -Verbose -WhatIf .NOTES General notes #> [CmdletBinding(SupportsShouldProcess)] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [parameter(Mandatory)][ValidateSet('MissingAtLeastOne', 'MissingAll')][string] $Type ) Write-Verbose -Message "Remove-WinADDFSTopology - Getting topology" $Topology = Get-WinADDFSTopology -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Type $Type foreach ($Object in $Topology) { Write-Verbose -Message "Remove-WinADDFSTopology - Removing '$($Object.Name)' with status '$($Object.Status)' / DN: '$($Object.DistinguishedName)' using '$($Object.QueryServer)'" try { Remove-ADObject -Identity $Object.DistinguishedName -Server $Object.QueryServer -Confirm:$false -ErrorAction Stop } catch { Write-Warning -Message "Remove-WinADDFSTopology - Failed to remove '$($Object.Name)' with status '$($Object.Status)' / DN: '$($Object.DistinguishedName)' using '$($Object.QueryServer)'. Error: $($_.Exception.Message)" } } } 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) { If ($Duplicate.ProtectedFromAccidentalDeletion -eq $true) { Try { Set-ADObject -Identity $($Duplicate.ObjectGUID) -ProtectedFromAccidentalDeletion $false -ErrorAction Stop -Server $Duplicate.Server } Catch { Write-Warning "Skipped object GUID: $($Duplicate.ObjectGUID) from deletion, failed to remove ProtectedFromAccidentalDeletion" Write-Verbose "Error message $($_.Exception.Message)" Continue } } $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.Server } 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] $FromEmail, [string] $ToEmail, [switch] $Display, [Array] $AddSecondary #, # [switch] $UpdateMailNickName ) $Summary = [ordered] @{ SamAccountName = $ADUser.SamAccountName UserPrincipalName = $ADUser.UserPrincipalName EmailAddress = '' ProxyAddresses = '' EmailAddressStatus = 'Not required' ProxyAddressesStatus = 'Not required' EmailAddressError = '' ProxyAddressesError = '' } $RequiredProperties = @( 'EmailAddress' 'proxyAddresses' #'mailNickName' ) 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 #MailNickName = $ProcessUser.mailNickName } if (-not $ToEmail) { # We didn't wanted to change primary email address so we use whatever is set $ExpectedUser.EmailAddress = $ProcessUser.EmailAddress $ExpectedUser.Primary = $ProcessUser.Primary # this is case where Proxy Addresses of current user don't have email address set as primary # we want to fix the user right? if (-not $ExpectedUser.Primary -and $ExpectedUser.EmailAddress) { $ExpectedUser.Primary = $ExpectedUser.EmailAddress } } # if ($UpdateMailNickName) { #} # Lets add expected primary to proxy addresses we need $MakePrimary = "SMTP:$($ExpectedUser.EmailAddress)" $ProxyAddresses.Add($MakePrimary) # Lets add expected secondary to proxy addresses we need $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()) } } # Lets fix primary email address $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'] = '' } } # lets compare Expected Proxy Addresses, against current list # lets make sure in new proxy list we have only unique addresses, so if there are duplicates in existing one it will be replaced # We need to also convert it to [string[]] as Set-ADUser with -Replace is very picky about it # Replacement for Sort-Object -Unique which removes primary SMTP: if it's duplicate of smtp: $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 ';' # we need to compare case sensitive 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 } } <# if ($FromEmail -and $FromEmail -like '*@*') { if ($FromEmail -ne $ToEmail) { $FindSecondary = "SMTP:$FromEmail" if ($ProcessUser.Primary -contains $FromEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $FindSecondary will be removed from proxy addresses as primary (1)")) { Set-ADUser -Identity $ADUser -Remove @{ proxyAddresses = $FindSecondary } } } $MakeSecondary = "smtp:$FromEmail" if ($ProcessUser.Secondary -notcontains $FromEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $MakeSecondary will be added to proxy addresses as secondary (2)")) { Set-ADUser -Identity $ADUser -Add @{ proxyAddresses = $MakeSecondary } } } } } if ($ToEmail -and $ToEmail -like '*@*') { if ($ProcessUser.EmailAddress -ne $ToEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $ToEmail will be set in EmailAddresss field (3)")) { Set-ADUser -Identity $ADUser -EmailAddress $ToEmail } } if ($ProcessUser.Secondary -contains $ToEmail) { $RemovePotential = "smtp:$ToEmail" if ($PSCmdlet.ShouldProcess($ADUser, "Email $RemovePotential will be removed from proxy addresses (4)")) { Set-ADUser -Identity $ADUser -Remove @{ proxyAddresses = $RemovePotential } } } $MakePrimary = "SMTP:$ToEmail" if ($ProcessUser.Primary.Count -in @(0, 1) -and $ProcessUser.Primary -notcontains $ToEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $MakePrimary will be added to proxy addresses as primary (5)")) { Set-ADUser -Identity $ADUser -Add @{ proxyAddresses = $MakePrimary } } } elseif ($ProcessUser.Primary.Count -gt 1) { [Array] $PrimaryEmail = $ProcessUser.Primary | Sort-Object -Unique if ($PrimaryEmail.Count -eq 1) { if ($PrimaryEmail -ne $ToEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $MakePrimary will be added to proxy addresses as primary (6)")) { Set-ADUser -Identity $ADUser -Add @{ proxyAddresses = $MakePrimary } } } else { if ($ProcessUser.Secondary -notcontains $PrimaryEmail) { $MakeSecondary = "smtp:$PrimaryEmail" if ($PSCmdlet.ShouldProcess($ADUser, "Email $MakeSecondary will be added to proxy addresses as secondary (7)")) { Set-ADUser -Identity $ADUser -Add @{ proxyAddresses = $MakeSecondary } } } } } else { foreach ($Email in $PrimaryEmail) { } } } if ($ProcessUser.Primary -notcontains $ToEmail) { #if ($PSCmdlet.ShouldProcess($ADUser, "Email $MakePrimary will be added to proxy addresses as primary (6)")) { # Set-ADUser -Identity $ADUser -Add @{ proxyAddresses = $MakePrimary } #} } } if ($Display) { $ProcessUser } #> <# if ($FromEmail -and $FromEmail -like '*@*') { if ($FromEmail -ne $ToEmail) { $FindSecondary = "SMTP:$FromEmail" if ($ADUser.ProxyAddresses -ccontains $FindSecondary) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $FindSecondary will be removed from proxy addresses as primary (1)")) { Set-ADUser -Identity $ADUser -Remove @{ proxyAddresses = $FindSecondary } } } $MakeSecondary = "smtp:$FromEmail" if ($ADUser.ProxyAddresses -cnotcontains $MakeSecondary) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $MakeSecondary will be added to proxy addresses as secondary (2)")) { Set-ADUser -Identity $ADUser -Add @{ proxyAddresses = $MakeSecondary } } } } } if ($ToEmail -and $ToEmail -like '*@*') { $RemovePotential = "smtp:$ToEmail" $MakePrimary = "SMTP:$ToEmail" if ($ADUser.EmailAddress -ne $ToEmail) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $ToEmail will be set in EmailAddresss field (3)")) { Set-ADUser -Identity $ADUser -EmailAddress $ToEmail } } if ($ADUser.ProxyAddresses -ccontains $RemovePotential) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $RemovePotential will be removed from proxy addresses (4)")) { Set-ADUser -Identity $ADUser -Remove @{ proxyAddresses = $RemovePotential } } } if ($ADUser.ProxyAddresses -cnotcontains $MakePrimary) { if ($PSCmdlet.ShouldProcess($ADUser, "Email $MakePrimary will be added to proxy addresses as primary (5)")) { Set-ADUser -Identity $ADUser -Add @{ proxyAddresses = $MakePrimary } } } } #> #} 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 Request-ChangePasswordAtLogon { <# .SYNOPSIS This command will find all users that have expired password and set them to change password at next logon. .DESCRIPTION This command will find all users that have expired password and set them to change password at next logon. This is useful for example for Azure AD Connect where you want to force users to change password on next logon. The password expiration doesn't get synced in specific conditions to Azure AD so you need to do it manually. .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 LimitProcessing Provide limit of objects that will be processed in a single run .PARAMETER IgnoreDisplayName Allow to ignore certain users based on their DisplayName. -It uses -like operator so you can use wildcards. This is useful for example for Exchange accounts that have expired password but are not used for anything else. .PARAMETER IgnoreDistinguishedName Allow to ignore certain users based on their DistinguishedName. It uses -like operator so you can use wildcards. .PARAMETER IgnoreSamAccountName Allow to ignore certain users based on their SamAccountName. It uses -like operator so you can use wildcards. .PARAMETER OrganizationalUnit Provide a list of Organizational Units to search for users that have expired password. If not provided, all users in the forest will be searched. .PARAMETER PassThru Returns objects that were processed. .EXAMPLE $OU = @( 'OU=Default,OU=Users.NoSync,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz' 'OU=Administrative,OU=Users.NoSync,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz' ) Request-ChangePasswordAtLogon -OrganizationalUnit $OU -LimitProcessing 1 -PassThru -Verbose -WhatIf | Format-Table .NOTES Please note that for Azure AD to pickup the change, you may need: Get-ADSyncAADCompanyFeature Set-ADSyncAADCompanyFeature -ForcePasswordChangeOnLogOn $true As described in https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-password-hash-synchronization#synchronizing-temporary-passwords-and-force-password-change-on-next-logon The above is not required only for new users without a password set. If the password is set the feature is required. #> [CmdletBinding(SupportsShouldProcess)] param ( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [int] $LimitProcessing, [Array] $IgnoreDisplayName, [Array] $IgnoreDistinguishedName, [Array] $IgnoreSamAccountName, [string[]] $OrganizationalUnit, [switch] $PassThru ) Begin { $ForestDetails = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains $IgnoreDisplayNameTotal = @( 'Microsoft Exchange*' foreach ($I in $IgnoreDisplayName) { $I } ) $IgnoreDistinguishedNameTotal = @( "*,CN=Users,*" foreach ($I in $IgnoreDistinguishedName) { $I } ) $IgnoreSamAccountNameTotal = @( 'Administrator' 'Guest' 'krbtgt*' 'healthmailbox*' foreach ($I in $IgnoreSamAccountName) { $I } ) } Process { [Array] $UsersFound = foreach ($Domain in $ForestDetails.Domains) { $QueryServer = $ForestDetails['QueryServers'][$Domain].HostName[0] if ($OrganizationalUnit) { $Users = @( foreach ($OU in $OrganizationalUnit) { $OUDomain = ConvertFrom-DistinguishedName -DistinguishedName $OU -ToDomainCN if ($OUDomain -eq $Domain) { Get-ADUser -Filter "Enabled -eq '$true'" -Properties DisplayName, SamAccountName, PasswordExpired, PasswordLastSet, pwdLastSet, PasswordNeverExpires -Server $QueryServer -SearchBase $OU } } ) $Users = $Users | Sort-Object -Property DistinguishedName -Unique } else { $Users = Get-ADUser -Filter "Enabled -eq '$true'" -Properties DisplayName, SamAccountName, PasswordExpired, PasswordLastSet, pwdLastSet, PasswordNeverExpires -Server $QueryServer } :SkipUser foreach ($User in $Users) { # lets asses if password is set to expire or not $DateExpiry = $null if ($User."msDS-UserPasswordExpiryTimeComputed" -ne 9223372036854775807) { # This is standard situation where users password is expiring as needed try { $DateExpiry = ([datetime]::FromFileTime($User."msDS-UserPasswordExpiryTimeComputed")) } catch { $DateExpiry = $User."msDS-UserPasswordExpiryTimeComputed" } } if ($User.pwdLastSet -eq 0 -and $DateExpiry.Year -eq 1601) { $PasswordAtNextLogon = $true } else { $PasswordAtNextLogon = $false } if ($User.PasswordExpired -eq $true -and $PasswordAtNextLogon -eq $false -and $User.PasswordNeverExpires -eq $false) { foreach ($I in $IgnoreSamAccountNameTotal) { if ($User.SamAccountName -like $I) { Write-Verbose -Message "Request-ChangePasswordOnExpiry - Ignoring $($User.SamAccountName) / $($User.DistinguishedName)" continue SkipUser } } foreach ($I in $IgnoreDistinguishedNameTotal) { if ($User.DistinguishedName -like $I) { Write-Verbose -Message "Request-ChangePasswordOnExpiry - Ignoring $($User.SamAccountName) / $($User.DistinguishedName)" continue SkipUser } } foreach ($I in $IgnoreDisplayNameTotal) { if ($User.DisplayName -like $I) { Write-Verbose -Message "Request-ChangePasswordOnExpiry - Ignoring $($User.SamAccountName) / $($User.DistinguishedName)" continue SkipUser } } [PSCustomObject] @{ SamAccountName = $User.SamAccountName Domain = $Domain DisplayName = $User.DisplayName DistinguishedName = $User.DistinguishedName PasswordExpired = $User.PasswordExpired PasswordLastSet = $User.PasswordLastSet PasswordNeverExpires = $User.PasswordNeverExpires } } else { Write-Verbose -Message "Request-ChangePasswordOnExpiry - Skipping $($User.SamAccountName) / $($User.DistinguishedName) - Password already requested at next logon or never expires." } } } $Count = 0 Write-Verbose -Message "Request-ChangePasswordOnExpiry - Found $($UsersFound.Count) expired users. Processing..." foreach ($User in $UsersFound) { if ($LimitProcessing -and $Count -ge $LimitProcessing) { break } Write-Verbose -Message "Request-ChangePasswordOnExpiry - Setting $($User.SamAccountName) to change password on next logon / $($User.Domain)" Set-ADUser -ChangePasswordAtLogon $true -Identity $User.SamAccountName -Server $ForestDetails['QueryServers'][$User.Domain].HostName[0] if ($PassThru) { $User } $Count++ } } } function Request-DisableOnAccountExpiration { <# .SYNOPSIS This command will find all users that have expired account and set them to be disabled. .DESCRIPTION This command will find all users that have expired account and set them to be disabled. This is useful for example for Azure AD Connect where you want to disable users that have expired account. The account expiration doesn't get synced in specific conditions to Azure AD so you need to do it manually. .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 LimitProcessing Provide limit of objects that will be processed in a single run .PARAMETER IgnoreDisplayName Allow to ignore certain users based on their DisplayName. -It uses -like operator so you can use wildcards. This is useful for example for Exchange accounts that have expired password but are not used for anything else. .PARAMETER IgnoreDistinguishedName Allow to ignore certain users based on their DistinguishedName. It uses -like operator so you can use wildcards. .PARAMETER IgnoreSamAccountName Allow to ignore certain users based on their SamAccountName. It uses -like operator so you can use wildcards. .PARAMETER OrganizationalUnit Provide a list of Organizational Units to search for users that have expired password. If not provided, all users in the forest will be searched. .PARAMETER PassThru Returns objects that were processed. .EXAMPLE Request-DisableOnAccountExpiration -LimitProcessing 1 -PassThru -Verbose -WhatIf | Format-Table .EXAMPLE $OU = @( 'OU=Default,OU=Users.NoSync,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz' 'OU=Administrative,OU=Users.NoSync,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz' ) Request-DisableOnAccountExpiration -LimitProcessing 1 -PassThru -Verbose -WhatIf -OrganizationalUnit $OU | Format-Table .NOTES General notes #> [cmdletbinding(SupportsShouldProcess)] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [int] $LimitProcessing, [Array] $IgnoreDisplayName, [Array] $IgnoreDistinguishedName, [Array] $IgnoreSamAccountName, [string[]] $OrganizationalUnit, [switch] $PassThru ) Begin { $Today = Get-Date $ForestDetails = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains $IgnoreDisplayNameTotal = @( 'Microsoft Exchange*' foreach ($I in $IgnoreDisplayName) { $I } ) $IgnoreDistinguishedNameTotal = @( "*,CN=Users,*" foreach ($I in $IgnoreDistinguishedName) { $I } ) $IgnoreSamAccountNameTotal = @( 'Administrator' 'Guest' 'krbtgt*' 'healthmailbox*' foreach ($I in $IgnoreSamAccountName) { $I } ) } Process { [Array] $UsersFound = foreach ($Domain in $ForestDetails.Domains) { $QueryServer = $ForestDetails['QueryServers'][$Domain].HostName[0] if ($OrganizationalUnit) { $Users = @( foreach ($OU in $OrganizationalUnit) { $OUDomain = ConvertFrom-DistinguishedName -DistinguishedName $OU -ToDomainCN if ($OUDomain -eq $Domain) { Get-ADUser -Filter "Enabled -eq '$true'" -Properties DisplayName, SamAccountName, PasswordExpired, PasswordLastSet, pwdLastSet, PasswordNeverExpires, AccountExpirationDate -Server $QueryServer -SearchBase $OU } } ) $Users = $Users | Sort-Object -Property DistinguishedName -Unique } else { $Users = Get-ADUser -Filter "Enabled -eq '$true'" -Properties DisplayName, SamAccountName, PasswordExpired, PasswordLastSet, pwdLastSet, PasswordNeverExpires, AccountExpirationDate -Server $QueryServer } :SkipUser foreach ($User in $Users) { # lets asses if password is set to expire or not $DateExpiry = $null if ($User."msDS-UserPasswordExpiryTimeComputed" -ne 9223372036854775807) { # This is standard situation where users password is expiring as needed try { $DateExpiry = ([datetime]::FromFileTime($User."msDS-UserPasswordExpiryTimeComputed")) } catch { $DateExpiry = $User."msDS-UserPasswordExpiryTimeComputed" } } if ($User.pwdLastSet -eq 0 -and $DateExpiry.Year -eq 1601) { $PasswordAtNextLogon = $true } else { $PasswordAtNextLogon = $false } if ($User.Enabled -eq $true -and $null -ne $User.AccountExpirationDate) { if ($User.AccountExpirationDate -le $Today) { foreach ($I in $IgnoreSamAccountNameTotal) { if ($User.SamAccountName -like $I) { Write-Verbose -Message "Request-DisableOnAccountExpiration - Ignoring $($User.SamAccountName) / $($User.DistinguishedName)" continue SkipUser } } foreach ($I in $IgnoreDistinguishedNameTotal) { if ($User.DistinguishedName -like $I) { Write-Verbose -Message "Request-DisableOnAccountExpiration - Ignoring $($User.SamAccountName) / $($User.DistinguishedName)" continue SkipUser } } foreach ($I in $IgnoreDisplayNameTotal) { if ($User.DisplayName -like $I) { Write-Verbose -Message "Request-DisableOnAccountExpiration - Ignoring $($User.SamAccountName) / $($User.DistinguishedName)" continue SkipUser } } Write-Verbose -Message "Request-DisableOnAccountExpiration - Found $($User.SamAccountName) / $Domain. Expiration date reached '$($User.AccountExpirationDate)'" [PSCustomObject] @{ SamAccountName = $User.SamAccountName Domain = $Domain DisplayName = $User.DisplayName AccountExpirationDate = $User.AccountExpirationDate PasswordAtNextLogon = $PasswordAtNextLogon PasswordExpired = $User.PasswordExpired PasswordLastSet = $User.PasswordLastSet PasswordNeverExpires = $User.PasswordNeverExpires DistinguishedName = $User.DistinguishedName } } else { Write-Verbose -Message "Request-DisableOnAccountExpiration - Skipping $($User.SamAccountName) / $Domain. Expiration date not reached '$($User.AccountExpirationDate)'" } } } } $Count = 0 if ($LimitProcessing) { Write-Verbose -Message "Request-DisableOnAccountExpiration - Found $($UsersFound.Count) expired users. Processing on disablement with limit of $LimitProcessing..." } else { Write-Verbose -Message "Request-DisableOnAccountExpiration - Found $($UsersFound.Count) expired users. Processing on disablement..." } foreach ($User in $UsersFound) { if ($LimitProcessing -and $Count -ge $LimitProcessing) { break } Write-Verbose -Message "Request-DisableOnAccountExpiration - Setting $($User.SamAccountName) to be disabled / $($User.Domain)" Set-ADUser -Enabled $false -Identity $User.SamAccountName -Server $ForestDetails['QueryServers'][$User.Domain].HostName[0] if ($PassThru) { $User } $Count++ } } } function Restore-ADACLDefault { <# .SYNOPSIS Restore default permissions for given object in Active Directory .DESCRIPTION Restore default permissions for given object in Active Directory. Equivalent of right click on object in Active Directory Users and Computers and selecting 'Restore defaults' .PARAMETER Object Specifies Active Directory objects to restore default permissions. This parameter is mandatory. .PARAMETER RemoveInheritedAccessRules Indicates whether to remove inherited ACEs from the object or principal. If this switch is specified, inherited ACEs are removed from the object or principal. If this switch is not specified, inherited ACEs are retained on the object or principal. .EXAMPLE $ObjectCheck = Get-ADObject -Id 'OU=_root,DC=ad,DC=evotec,DC=xyz' -Properties 'NtSecurityDescriptor', 'DistinguishedName' Restore-ADACLDefault -Object $ObjectCheck -Verbose .EXAMPLE Restore-ADACLDefault -Object 'OU=ITR01,DC=ad,DC=evotec,DC=xyz' -RemoveInheritedAccessRules -Verbose -WhatIf .NOTES Please be aware that when you use Restore-ADACLDefault it clears up existing permissions which may cut you out. #> [CmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)][alias('Identity')][Object] $Object, [switch] $RemoveInheritedAccessRules ) # lets get our forest details if (-not $Script:ForestDetails) { Write-Verbose "Restore-ADACLDefault - Gathering Forest Details" $Script:ForestDetails = Get-WinADForestDetails } # Lets get our schema if (-not $Script:RootDSESchema) { $Script:RootDSESchema = (Get-ADRootDSE).SchemaNamingContext } # lets try to asses what we have for object and if not get it properly if ($Object) { if ($Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) { If ($Object.DistinguishedName -and $Object.NtSecurityDescriptor) { # We have what we need } else { $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Object.DistinguishedName $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] $Object = Get-ADObject -Id $Object.DistinguishedName -Properties 'NtSecurityDescriptor', 'DistinguishedName' -Server $QueryServer } } elseif ($Object -is [string]) { $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Object $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] $Object = Get-ADObject -Id $Object -Properties 'NtSecurityDescriptor', 'DistinguishedName' -Server $QueryServer } else { Write-Warning -Message "Restore-ADACLDefault - Unknown object type $($Object.GetType().FullName)" return } } else { $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Object $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] $Object = Get-ADObject -Id $Object -Properties 'NtSecurityDescriptor', 'DistinguishedName' -Server $QueryServer } # We have our object, now lets get the default permissions for given type if ($Object.ObjectClass -eq 'Unknown') { Write-Verbose -Message "Restore-ADACLDefault - Unknown object type $($Object.ObjectClass), using default filter for Organizational-Unit" $Filter = 'name -eq "Organizational-Unit"' } else { $Class = $($Object.ObjectClass) $Filter = "lDAPDisplayName -eq '$Class'" } Write-Verbose "Restore-ADACLDefault - Getting default permissions from $Script:RootDSESchema using filter $Filter" #$ADObject = Get-ADObject -Filter $Filter -SearchBase $Script:RootDSESchema -Properties defaultSecurityDescriptor $DefaultPermissionsObject = Get-ADObject -Filter $Filter -SearchBase (Get-ADRootDSE).SchemaNamingContext -Properties defaultSecurityDescriptor, canonicalName, lDAPDisplayName if (-not $DefaultPermissionsObject.defaultsecuritydescriptor) { Write-Warning -Message "Restore-ADACLDefault - Unable to find default permissions for $($Object.ObjectClass)" return } $Descriptor = $DefaultPermissionsObject.defaultsecuritydescriptor $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Object.DistinguishedName $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] #Write-Verbose -Message "Restore-ADACLDefault - Disabling inheritance for $($Object.DistinguishedName)" #Disable-ADACLInheritance -ADObject $Object.DistinguishedName -RemoveInheritedAccessRules -Verbose #Write-Verbose -Message "Restore-ADACLDefault - Removing permissions for $($Object.DistinguishedName)" #Remove-ADACL -ADObject $Object.DistinguishedName # $Descriptor | ConvertFrom-SddlString -Type ActiveDirectoryRights # $SecurityDescriptor = [System.DirectoryServices.ActiveDirectorySecurity]::new() # $SecurityDescriptor.SetSecurityDescriptorSddlForm($Descriptor) # $SecurityDescriptor $Object.NtSecurityDescriptor.SetSecurityDescriptorSddlForm($Descriptor) Write-Verbose "Restore-ADACLDefault - Saving permissions for $($Object.DistinguishedName) on $($QueryServer)" Set-ADObject -Identity $Object.DistinguishedName -Replace @{ ntSecurityDescriptor = $Object.NtSecurityDescriptor } -ErrorAction Stop -Server $QueryServer if ($RemoveInheritedAccessRules) { Write-Verbose -Message "Restore-ADACLDefault - Disabling inheritance for $($Object.DistinguishedName)" Disable-ADACLInheritance -ADObject $Object.DistinguishedName -RemoveInheritedAccessRules } } <# Code to use to find default permissions for given object type $Object = Get-ADObject -Id 'OU=_root,DC=ad,DC=evotec,DC=xyz' -Properties 'NtSecurityDescriptor', 'DistinguishedName' $Class = $($Object.ObjectClass) $List = Get-ADObject -Filter "lDAPDisplayName -eq '$Class'" -SearchBase (Get-ADRootDSE).SchemaNamingContext -Properties defaultSecurityDescriptor, canonicalName, lDAPDisplayName $List #> function Set-ADACL { [cmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [alias('Identity')][string] $ADObject, [Parameter(Mandatory)][Array] $ACLSettings, [Parameter(Mandatory)][ValidateSet('Enabled', 'Disabled')] $Inheritance, [switch] $Suppress ) $Results = @{ Add = [System.Collections.Generic.List[PSCustomObject]]::new() Remove = [System.Collections.Generic.List[PSCustomObject]]::new() Skip = [System.Collections.Generic.List[PSCustomObject]]::new() Warnings = [System.Collections.Generic.List[string]]::new() Errors = [System.Collections.Generic.List[string]]::new() } $CachedACL = [ordered] @{} $ExpectedProperties = @('ActiveDirectoryRights', 'AccessControlType', 'ObjectTypeName', 'InheritedObjectTypeName', 'InheritanceType') $FoundDisprepancy = $false $Count = 1 foreach ($ACL in $ACLSettings) { if ($ACL.Action -eq 'Skip') { continue } elseif ($ACL.Action -eq 'Copy') { continue } # Check if all properties are present if ($ACL.Principal -and $ACL.Permissions) { foreach ($Permission in $ACL.Permissions) { if ($Permission -is [System.Collections.IDictionary]) { Compare-Object -ReferenceObject $ExpectedProperties -DifferenceObject @($Permission.Keys) | Where-Object { $_.SideIndicator -in '<=' } | ForEach-Object { Write-Warning -Message "Set-ADACL - Entry $Count - $($ACL.Principal) is missing property $($_.InputObject) - provided only $($Permission.Keys)" $FoundDisprepancy = $true } } else { Compare-Object -ReferenceObject $ExpectedProperties -DifferenceObject @($Permission.PSObject.Properties.Name) | Where-Object { $_.SideIndicator -in '<=' } | ForEach-Object { Write-Warning -Message "Set-ADACL - Entry $Count - $($ACL.Principal) is missing property $($_.InputObject) - provided only $($Permission.PSObject.Properties.Name)" $FoundDisprepancy = $true } } } } elseif ($ACL.Principal) { if ($ACL -is [System.Collections.IDictionary]) { Compare-Object -ReferenceObject $ExpectedProperties -DifferenceObject @($ACL.Keys) | Where-Object { $_.SideIndicator -in '<=' } | ForEach-Object { Write-Warning -Message "Set-ADACL - Entry $Count - $($ACL.Principal) is missing property $($_.InputObject) - provided only $($ACL.Keys)" $FoundDisprepancy = $true } } else { Compare-Object -ReferenceObject $ExpectedProperties -DifferenceObject @($ACL.PSObject.Properties.Name) | Where-Object { $_.SideIndicator -in '<=' } | ForEach-Object { Write-Warning -Message "Set-ADACL - Entry $Count - $($ACL.Principal) is missing property $($_.InputObject) - provided only $($ACL.PSObject.Properties.Name)" $FoundDisprepancy = $true } } } $Count++ } if ($FoundDisprepancy) { Write-Warning -Message "Set-ADACL - Please check your ACL configuration is correct. Each entry must have the following properties: $($ExpectedProperties -join ', ')" $Results.Warnings.Add("Please check your ACL configuration is correct. Each entry must have the following properties: $($ExpectedProperties -join ', ')") if (-not $Suppress) { return $Results } else { return } } foreach ($ExpectedACL in $ACLSettings) { if ($ExpectedACL.Principal -and $ExpectedACL.Permissions) { foreach ($Principal in $ExpectedACL.Principal) { $ConvertedIdentity = Convert-Identity -Identity $Principal -Verbose:$false if ($ConvertedIdentity.Error) { Write-Warning -Message "Set-ADACL - Converting identity $($Principal) failed with $($ConvertedIdentity.Error). Be warned." $Results.Warnings.Add("Converting identity $($Principal) failed with $($ConvertedIdentity.Error). Be warned.") } $ConvertedPrincipal = ($ConvertedIdentity).Name if (-not $CachedACL[$ConvertedPrincipal]) { $CachedACL[$ConvertedPrincipal] = [ordered] @{} } # user may not provided any action, so we assume 'Set' as default $Action = if ($ExpectedACL.Action) { $ExpectedACL.Action } else { 'Add' } #$ExpectedACL.Action = $Action $CachedACL[$ConvertedPrincipal]['Action'] = $Action if (-not $CachedACL[$ConvertedPrincipal]['Permissions']) { $CachedACL[$ConvertedPrincipal]['Permissions'] = [System.Collections.Generic.List[object]]::new() } if ($ExpectedACL.Permissions) { foreach ($Permission in $ExpectedACL.Permissions) { $CachedACL[$ConvertedPrincipal]['Permissions'].Add([PSCustomObject] $Permission) } } } } elseif ($ExpectedACL.Principal) { foreach ($Principal in $ExpectedACL.Principal) { $ConvertedIdentity = Convert-Identity -Identity $Principal -Verbose:$false if ($ConvertedIdentity.Error) { Write-Warning -Message "Set-ADACL - Converting identity $($Principal) failed with $($ConvertedIdentity.Error). Be warned." } $ConvertedPrincipal = ($ConvertedIdentity).Name if (-not $CachedACL[$ConvertedPrincipal]) { $CachedACL[$ConvertedPrincipal] = [ordered] @{} } # user may not provided any action, so we assume 'Set' as default $Action = if ($ExpectedACL.Action) { $ExpectedACL.Action } else { 'Add' } #$ExpectedACL.Action = $Action $CachedACL[$ConvertedPrincipal]['Action'] = $Action if (-not $CachedACL[$ConvertedPrincipal]['Permissions']) { $CachedACL[$ConvertedPrincipal]['Permissions'] = [System.Collections.Generic.List[object]]::new() } $NewPermission = [ordered] @{} if ($ExpectedACL -is [System.Collections.IDictionary]) { foreach ($Key in $ExpectedACL.Keys) { if ($Key -notin @('Principal')) { $NewPermission.$Key = $ExpectedACL.$Key } } } else { foreach ($Property in $ExpectedACL.PSObject.Properties) { if ($Property.Name -notin @('Principal')) { $NewPermission.$($Property.Name) = $Property.Value } } } $CachedACL[$ConvertedPrincipal]['Permissions'].Add([PSCustomObject] $NewPermission) } } } $MainAccessRights = Get-ADACL -ADObject $ADObject -Bundle foreach ($CurrentACL in $MainAccessRights.ACLAccessRules) { $ConvertedIdentity = Convert-Identity -Identity $CurrentACL.Principal -Verbose:$false if ($ConvertedIdentity.Error) { Write-Warning -Message "Set-ADACL - Converting identity $($Principal) failed with $($ConvertedIdentity.Error). Be warned." $Results.Warnings.Add("Converting identity $($Principal) failed with $($ConvertedIdentity.Error). Be warned.") } $ConvertedPrincipal = ($ConvertedIdentity).Name if ($CachedACL[$ConvertedPrincipal]) { if ($CachedACL[$ConvertedPrincipal]['Action'] -eq 'Skip') { #Write-Verbose "Set-ADACL - Skipping $($CurrentACL.Principal)" $Results.Skip.Add( [PSCustomObject] @{ Principal = $ConvertedPrincipal AccessControlType = $CurrentACL.AccessControlType Action = 'Skip' Permissions = $CurrentACL } ) continue } else { Write-Verbose "Set-ADACL - Processing $($ConvertedPrincipal)" $DirectMatch = $false foreach ($SetPermission in $CachedACL[$ConvertedPrincipal].Permissions) { if ($CurrentACL.AccessControlType -eq $SetPermission.AccessControlType) { # since it's possible people will differently name their object type name, we are going to convert it to GUID $TypeObjectLeft = Convert-ADSchemaToGuid -SchemaName $CurrentACL.ObjectTypeName -AsString $TypeObjectRight = Convert-ADSchemaToGuid -SchemaName $SetPermission.ObjectTypeName -AsString if ($TypeObjectLeft -eq $TypeObjectRight) { if ($CurrentACL.ActiveDirectoryRights -eq $SetPermission.ActiveDirectoryRights) { if ($CurrentACL.InheritedObjectTypeName -eq $SetPermission.InheritedObjectTypeName) { if ($CurrentACL.InheritanceType -eq $SetPermission.InheritanceType) { $DirectMatch = $true } } } } } } if ($DirectMatch) { $Results.Skip.Add( [PSCustomObject] @{ Principal = $ConvertedPrincipal AccessControlType = $CurrentACL.AccessControlType Action = 'Skip' Permissions = $CurrentACL } ) } else { if ($Inheritance -eq 'Enabled' -and $CurrentACL.IsInherited) { # normally we would try to remove it, but it is inherited, so we will skip it $Results.Skip.Add( [PSCustomObject] @{ Principal = $ConvertedPrincipal AccessControlType = $CurrentACL.AccessControlType Action = 'Skip' Permissions = $CurrentACL } ) } else { $Results.Remove.Add( [PSCustomObject] @{ Principal = $ConvertedPrincipal AccessControlType = $CurrentACL.AccessControlType Action = 'Remove' Permissions = $CurrentACL } ) } } } } else { # we don't have this principal defined for set, needs to be removed Write-Verbose "Set-ADACL - Preparing for removal of $($ConvertedPrincipal)" if ($Inheritance -eq 'Enabled' -and $CurrentACL.IsInherited) { $Results.Skip.Add( [PSCustomObject] @{ Principal = $ConvertedPrincipal AccessControlType = $CurrentACL.AccessControlType Action = 'Skip' Permissions = $CurrentACL } ) } else { $Results.Remove.Add( [PSCustomObject] @{ Principal = $ConvertedPrincipal AccessControlType = $CurrentACL.AccessControlType Action = 'Remove' Permissions = $CurrentACL } ) } } } $AlreadyCovered = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($Principal in $CachedACL.Keys) { if ($CachedACL[$Principal]['Action'] -in 'Add', 'Set') { foreach ($SetPermission in $CachedACL[$Principal]['Permissions']) { $DirectMatch = $false foreach ($CurrentACL in $MainAccessRights.ACLAccessRules) { if ($CurrentACL -in $AlreadyCovered) { continue } $RequestedPrincipal = Convert-Identity -Identity $Principal -Verbose:$false $RequestedPrincipalFromACL = Convert-Identity -Identity $CurrentACL.Principal -Verbose:$false if ($RequestedPrincipalFromACL.Name -ne $RequestedPrincipal.Name) { continue } if ($CurrentACL.AccessControlType -eq $SetPermission.AccessControlType) { # since it's possible people will differently name their object type name, we are going to convert it to GUID $TypeObjectLeft = Convert-ADSchemaToGuid -SchemaName $CurrentACL.ObjectTypeName -AsString $TypeObjectRight = Convert-ADSchemaToGuid -SchemaName $SetPermission.ObjectTypeName -AsString if ($TypeObjectLeft -eq $TypeObjectRight) { if ($CurrentACL.ActiveDirectoryRights -eq $SetPermission.ActiveDirectoryRights) { if ($CurrentACL.InheritedObjectTypeName -eq $SetPermission.InheritedObjectTypeName) { if ($CurrentACL.InheritanceType -eq $SetPermission.InheritanceType) { $DirectMatch = $true $AlreadyCovered.Add($CurrentACL) } } } } } } if ($DirectMatch) { Write-Verbose -Message "Set-ADACL - Skipping $($Principal), as it already exists" } else { $Results.Add.Add( [PSCustomObject] @{ Principal = $Principal AccessControlType = $SetPermission.AccessControlType Action = 'Add' Permissions = $SetPermission } ) } } } } if (-not $WhatIfPreference) { Write-Verbose -Message "Set-ADACL - Applying changes to ACL" if ($Results.Remove.Permissions) { Write-Verbose -Message "Set-ADACL - Removing ACL" try { Remove-ADACL -ActiveDirectorySecurity $MainAccessRights.ACL -ACL $Results.Remove.Permissions } catch { Write-Warning -Message "Set-ADACL - Failed to remove ACL for at least one of principals $($Results.Remove.Principal -join ', ')" $Results.Errors.Add("Failed to remove ACL for $($Results.Remove.Principal -join ', ')") } } Write-Verbose -Message "Set-ADACL - Adding ACL" foreach ($Add in $Results.Add) { $addADACLSplat = @{ NTSecurityDescriptor = $MainAccessRights.ACL ADObject = $ADObject Principal = $Add.Principal AccessControlType = $Add.Permissions.AccessControlType AccessRule = $Add.Permissions.ActiveDirectoryRights ObjectType = $Add.Permissions.ObjectTypeName InheritanceType = $Add.Permissions.InheritanceType InheritedObjectType = $Add.Permissions.InheritedObjectTypeName } try { Add-ADACL @addADACLSplat } catch { Write-Warning -Message "Set-ADACL - Failed to add ACL for $($Add.Principal)" $Results.Errors.Add("Failed to add ACL for $($Add.Principal)") } } } if (-not $Suppress) { $Results } } function Set-ADACLInheritance { <# .SYNOPSIS Enables or Disables the inheritance of access control entries (ACEs) from parent objects for one or more Active Directory objects or security principals. .DESCRIPTION Enables or Disables the inheritance of access control entries (ACEs) from parent objects for one or more Active Directory objects or security principals. .PARAMETER ADObject Specifies one or more Active Directory objects or security principals to enable or disable inheritance of ACEs from parent objects. This parameter is mandatory when the 'ADObject' parameter set is used. .PARAMETER ACL Specifies one or more access control lists (ACLs) to enable or disable inheritance of ACEs from parent objects. This parameter is mandatory when the 'ACL' parameter set is used. .PARAMETER Inheritance Specifies whether to enable or disable inheritance of ACEs from parent objects. .PARAMETER RemoveInheritedAccessRules Indicates whether to remove inherited ACEs from the object or principal. .EXAMPLE Set-ADACLInheritance -ADObject 'CN=TestOU,DC=contoso,DC=com' -Inheritance 'Disabled' -RemoveInheritedAccessRules .EXAMPLE Set-ADACLInheritance -ACL $ACL -Inheritance 'Disabled' -RemoveInheritedAccessRules .EXAMPLE Set-ADACLInheritance -ADObject 'CN=TestOU,DC=contoso,DC=com' -Inheritance 'Enabled' .NOTES General notes #> [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ADObject')] param( [parameter(ParameterSetName = 'ADObject', Mandatory)][alias('Identity')][Array] $ADObject, [parameter(ParameterSetName = 'ACL', Mandatory)][Array] $ACL, [Parameter(Mandatory)][ValidateSet('Enabled', 'Disabled')] $Inheritance, [switch] $RemoveInheritedAccessRules ) if (-not $Script:ForestDetails) { Write-Verbose "Set-ADACLInheritance - Gathering Forest Details" $Script:ForestDetails = Get-WinADForestDetails } $PreserveInheritance = -not $RemoveInheritedAccessRules.IsPresent if ($ACL) { foreach ($A in $ACL) { # isProtected - true to protect the access rules associated with this ObjectSecurity object from inheritance; false to allow inheritance. # preserveInheritance - true to preserve inherited access rules; false to remove inherited access rules. This parameter is ignored if isProtected is false. if ($Inheritance -eq 'Enabled') { $A.ACL.SetAccessRuleProtection($false, -not $RemoveInheritedAccessRules.IsPresent) $Action = "Inheritance $Inheritance" Write-Verbose "Set-ADACLInheritance - Enabling inheritance for $($A.DistinguishedName)" } elseif ($Inheritance -eq 'Disabled') { $Action = "Inheritance $Inheritance, RemoveInheritedAccessRules $RemoveInheritedAccessRules" $A.ACL.SetAccessRuleProtection($true, $PreserveInheritance) Write-Verbose "Set-ADACLInheritance - Disabling inheritance for $($A.DistinguishedName) / Remove Inherited Rules: $($RemoveInheritedAccessRules.IsPresent)" } $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $A.DistinguishedName $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] if ($PSCmdlet.ShouldProcess($A.DistinguishedName, $Action)) { Write-Verbose "Set-ADACLInheritance - Saving permissions for $($A.DistinguishedName) on $QueryServer" try { Set-ADObject -Identity $A.DistinguishedName -Replace @{ ntSecurityDescriptor = $A.ACL } -ErrorAction Stop -Server $QueryServer } catch { Write-Warning "Set-ADACLInheritance - Saving permissions for $($A.DistinguishedName) on $QueryServer failed: $($_.Exception.Message)" } } } } else { foreach ($Object in $ADObject) { $getADACLSplat = @{ ADObject = $ADObject Bundle = $true Resolve = $true } $ACL = Get-ADACL @getADACLSplat # isProtected - true to protect the access rules associated with this ObjectSecurity object from inheritance; false to allow inheritance. # preserveInheritance - true to preserve inherited access rules; false to remove inherited access rules. This parameter is ignored if isProtected is false. if ($Inheritance -eq 'Enabled') { $ACL.ACL.SetAccessRuleProtection($false, -not $RemoveInheritedAccessRules.IsPresent) $Action = "Inheritance $Inheritance" Write-Verbose "Set-ADACLInheritance - Enabling inheritance for $($ACL.DistinguishedName)" } elseif ($Inheritance -eq 'Disabled') { $Action = "Inheritance $Inheritance, RemoveInheritedAccessRules $RemoveInheritedAccessRules" $ACL.ACL.SetAccessRuleProtection($true, $PreserveInheritance) Write-Verbose "Set-ADACLInheritance - Disabling inheritance for $($ACL.DistinguishedName) / Remove Inherited Rules: $($RemoveInheritedAccessRules.IsPresent)" } $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $ACL.DistinguishedName $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0] if ($PSCmdlet.ShouldProcess($ACL.DistinguishedName, $Action)) { Write-Verbose "Set-ADACLInheritance - Saving permissions for $($ACL.DistinguishedName) on $QueryServer" try { Set-ADObject -Identity $ACL.DistinguishedName -Replace @{ ntSecurityDescriptor = $ACL.ACL } -ErrorAction Stop -Server $QueryServer # Set-Acl -Path $ACL.Path -AclObject $ACL.ACL -ErrorAction Stop } catch { Write-Warning "Set-ADACLInheritance - Saving permissions for $($ACL.DistinguishedName) on $QueryServer failed: $($_.Exception.Message)" } } } } } function Set-ADACLOwner { [cmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory)][alias('Identity')][Array] $ADObject, [Parameter(Mandatory)][string] $Principal ) Begin { if ($Principal -is [string]) { if ($Principal -like '*/*') { $SplittedName = $Principal -split '/' [System.Security.Principal.IdentityReference] $PrincipalIdentity = [System.Security.Principal.NTAccount]::new($SplittedName[0], $SplittedName[1]) } else { [System.Security.Principal.IdentityReference] $PrincipalIdentity = [System.Security.Principal.NTAccount]::new($Principal) } } else { # Not yet ready return } } Process { foreach ($Object in $ADObject) { #$ADObjectData = $null if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) { # if object already has proper security descriptor we don't need to do additional querying #if ($Object.ntSecurityDescriptor) { # $ADObjectData = $Object #} [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 # -ObjectDN $DistinguishedName if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) { Write-Warning "Set-ADACLOwner - Drive $DNConverted not mapped. Terminating..." continue } } $PathACL = "$DNConverted`:\$($DistinguishedName)" try { $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop } catch { Write-Warning "Get-ADACL - Path $DistinguishedName / $PathACL - Error: $($_.Exception.Message)" continue } <# if (-not $ADObjectData) { try { $ADObjectData = Get-ADObject -Identity $DistinguishedName -Properties ntSecurityDescriptor -ErrorAction Stop $ACLs = $ADObjectData.ntSecurityDescriptor } catch { Write-Warning "Get-ADACL - Path $DistinguishedName - Error: $($_.Exception.Message)" continue } } #> $CurrentOwner = $ACLs.Owner Write-Verbose "Set-ADACLOwner - Changing owner from $($CurrentOwner) to $PrincipalIdentity for $($DistinguishedName)" try { $ACLs.SetOwner($PrincipalIdentity) } catch { Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $PrincipalIdentity for $($DistinguishedName): $($_.Exception.Message)" continue } try { #Set-ADObject -Identity $DistinguishedName -Replace @{ ntSecurityDescriptor = $ACLs } -ErrorAction Stop Set-Acl -Path $PathACL -AclObject $ACLs -ErrorAction Stop } catch { Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $PrincipalIdentity for $($DistinguishedName): $($_.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', #New to Windows Server 2003: 'Group Caching', 'Linked-Value Replication', 'DS RPC Client', 'DS RPC Server', 'DS Schema', #New to Windows Server 2012 and Windows 8: 'Transformation Engine', 'Claims-Based Access Control', # Added, but not setting in same place 'Netlogon' )][string[]] $Diagnostics, #[ValidateSet('None', 'Minimal', 'Basic', 'Extensive', 'Verbose', 'Internal')] [string] $Level, [System.Collections.IDictionary] $ExtendedForestInformation ) <# Levels 0 (None): Only critical events and error events are logged at this level. This is the default setting for all entries, and it should be modified only if a problem occurs that you want to investigate. 1 (Minimal): Very high-level events are recorded in the event log at this setting. Events may include one message for each major task that is performed by the service. Use this setting to start an investigation when you do not know the location of the problem. 2 (Basic) 3 (Extensive): This level records more detailed information than the lower levels, such as steps that are performed to complete a task. Use this setting when you have narrowed the problem to a service or a group of categories. 4 (Verbose) 5 (Internal): This level logs all events, including debug strings and configuration changes. A complete log of the service is recorded. Use this setting when you have traced the problem to a particular category of a small set of categories. #> $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' #New to Windows Server 2003: = #New to Windows Server 2003: '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' #New to Windows Server 2012 and Windows 8: = #New to Windows Server 2012 and Windows 8: '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') { # https://support.microsoft.com/en-us/help/109626/enabling-debug-logging-for-the-netlogon-service # Weirdly enough nltest sets it as REG_SZ and article above says REG_DWORD if ($Level -eq 'None') { # nltest /dbflag:0x2080ffff # Enable 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 { # nltest /dbflag:0x0 # Disable 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 } # Retart of NetLogon service is not required. } } } } } } [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-WinADDomainControllerOption { <# .SYNOPSIS Command to set the options of a domain controller .DESCRIPTION Command to set the options of a domain controller that uses the repadmin command Available options: - DISABLE_OUTBOUND_REPL: Disables outbound replication. - DISABLE_INBOUND_REPL: Disables inbound replication. - DISABLE_NTDSCONN_XLATE: Disables the translation of NTDSConnection objects. - DISABLE_SPN_REGISTRATION: Disables Service Principal Name (SPN) registration. - IS_GC: Sets or unsets the Global Catalog (GC) for the domain controller. .PARAMETER DomainController The domain controller to set the options on .PARAMETER Option Choose one or more options from the list of available options to enable or disable Options: - DISABLE_OUTBOUND_REPL: Disables outbound replication. - DISABLE_INBOUND_REPL: Disables inbound replication. - DISABLE_NTDSCONN_XLATE: Disables the translation of NTDSConnection objects. - DISABLE_SPN_REGISTRATION: Disables Service Principal Name (SPN) registration. - IS_GC: Sets or unsets the Global Catalog (GC) for the domain controller. .PARAMETER Action Choose to enable or disable the option(s) .EXAMPLE Set-WinADDomainControllerOption -DomainController 'ADRODC' -Option 'IS_GC' -Action Enable .NOTES General notes #> [cmdletBinding()] param( [parameter(Mandatory)][string]$DomainController, [ValidateSet( "DISABLE_OUTBOUND_REPL", "DISABLE_INBOUND_REPL", "DISABLE_NTDSCONN_XLATE", "DISABLE_SPN_REGISTRATION", "IS_GC" )] [parameter(Mandatory)][string[]]$Option, [parameter(Mandatory)][ValidateSet("Enable", "Disable")][string]$Action ) # Validate Domain Controller input if (-not $DomainController) { Write-Host "Domain Controller is required." return } # Determine the action to be taken $actionFlag = switch ($Action) { "Enable" { "+" } "Disable" { "-" } } foreach ($O in $Option) { # Construct the repadmin command # Execute the repadmin command try { $NewOptions = $null $CurrentOptions = $null Write-Verbose -Message "Set-WinADDomainControllerOption - Executing repadmin /options $DomainController $actionFlag$O" $Output = & repadmin /options $DomainController $actionFlag$O if ($Output) { foreach ($O in $Output) { if ($O.StartsWith("Current DSA Options:")) { $Options = $O.Split(":")[1].Trim().Split(",") $CurrentOptions = foreach ($O in $Options) { $Value = $O.Trim() if ($Value) { $Value } } } elseif ($O.StartsWith("New DSA Options:")) { $Options = $O.Split(":")[1].Trim().Split(",") $NewOptions = foreach ($O in $Options) { $Value = $O.Trim() if ($Value) { $Value } } } } If ($CurrentOptions) { $Status = $true } else { $Status = $false } if ($CurrentOptions -eq $NewOptions) { $ActionStatus = $false } else { $ActionStatus = $true } [PSCustomObject] @{ DomainController = $DomainController Status = $Status Action = $Action ActionStatus = $ActionStatus ActionStatusText = if ($ActionStatus) { "Changed" } else { "No changes" } CurrentOptions = $CurrentOptions -split " " NewOptions = $NewOptions -split " " Output = $Output } } } catch { Write-Warning -Message "Set-WinADDomainControllerOption - Failed to execute repadmin /options $DomainController $actionFlag$O. Exception: $($_.Exception.Message)" } } } function Set-WinADForestACLOwner { <# .SYNOPSIS Replaces the owner of the ACLs on all the objects (to Domain Admins) in the forest (or specific domain) that are not Administrative or WellKnownAdministrative. .DESCRIPTION Replaces the owner of the ACLs on all the objects (to Domain Admins) in the forest (or specific domain) that are not Administrative or WellKnownAdministrative. .PARAMETER IncludeOwnerType Defines which object owners are to be included in the replacement. Options are: 'WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown' .PARAMETER ExcludeOwnerType Defines which object owners are to be included in the replacement. Options are: 'WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown' .PARAMETER LimitProcessing Provide limit of objects that will be processed in a single run .PARAMETER Principal Defines the principal to be used as the new owner. By default those are Domain Admins for all objects. If you want to use a different principal, you can specify it here. Not really useful as the idea is to always have Domain Admins as object owners. .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 ADAdministrativeGroups Ability to provide AD Administrative Groups from another command to speed up processing .EXAMPLE Set-WinADForestACLOwner -WhatIf -Verbose -LimitProcessing 2 -IncludeOwnerType 'NotAdministrative', 'Unknown' .NOTES General notes #> [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Include')] param( [parameter(Mandatory, ParameterSetName = 'Include')][validateSet('WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown')][string[]] $IncludeOwnerType, [parameter(Mandatory, ParameterSetName = 'Exclude')][validateSet('WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown')][string[]] $ExcludeOwnerType, [int] $LimitProcessing, [string] $Principal, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation, [System.Collections.IDictionary] $ADAdministrativeGroups ) $Count = 0 $getWinADACLForestSplat = @{ Owner = $true IncludeOwnerType = $IncludeOwnerType ExcludeOwnerType = $ExcludeOwnerType Forest = $Forest IncludeDomains = $IncludeDomains ExcludeDomains = $ExcludeDomains ExtendedForestInformation = $ExtendedForestInformation } Remove-EmptyValue -Hashtable $getWinADACLForestSplat if (-not $ADAdministrativeGroups) { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } Get-WinADACLForest @getWinADACLForestSplat | ForEach-Object { if (-not $Principal) { $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $_.DistinguishedName $Principal = $ADAdministrativeGroups[$DomainName]['DomainAdmins'] } $Count += 1 Set-ADACLOwner -ADObject $_.DistinguishedName -Principal $Principal if ($LimitProcessing -gt 0 -and $Count -ge $LimitProcessing) { break } } } 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-WinADDNSRecords { <# .SYNOPSIS Small command that gathers quick information about DNS Server records and shows them in HTML output .DESCRIPTION Small command that gathers quick information about DNS Server records and shows them in HTML output .PARAMETER FilePath Path to HTML file where it's saved. If not given temporary path is used .PARAMETER HideHTML Prevents HTML output from being displayed in browser after generation is done .PARAMETER Online Forces use of online CDN for JavaScript/CSS which makes the file smaller. Default - use offline. .EXAMPLE Show-WinADDNSRecords .EXAMPLE Show-WinADDNSRecords -FilePath C:\Temp\test.html .NOTES General notes #> [cmdletBinding()] param( [parameter(Mandatory)][string] $FilePath, [switch] $HideHTML, [switch] $Online, [switch] $TabPerZone ) # Gather data $DNSByName = Get-WinADDnsRecords -Prettify -IncludeDetails $DNSByIP = Get-WinADDnsIPAddresses -Prettify -IncludeDetails $DNSZones = Get-WinADDNSZones $CachedZones = [ordered] @{} if ($TabPerZone) { foreach ($DnsEntry in $DNSByName) { if (-not $CachedZones[$DnsEntry.Zone]) { $CachedZones[$DnsEntry.Zone] = [System.Collections.Generic.List[Object]]::new() } $CachedZones[$DnsEntry.Zone].Add($DnsEntry) } } # Create HTML :-) New-HTML { New-HTMLTab -Name 'DNS Zones' { New-HTMLTable -DataTable $DNSZones -DataStore JavaScript -Filtering } New-HTMLTab -Name "DNS by Name" { if ($TabPerZone) { foreach ($Zone in $CachedZones.Keys) { New-HTMLTab -Name $Zone { New-HTMLTable -DataTable $CachedZones[$Zone] -DataStore JavaScript -Filtering { New-HTMLTableCondition -Name 'Count' -ComparisonType number -Value 1 -BackgroundColor LightGreen New-HTMLTableCondition -Name 'Count' -ComparisonType number -Value 1 -Operator gt -BackgroundColor Orange New-HTMLTableConditionGroup -Logic AND { New-HTMLTableCondition -Name 'Count' -ComparisonType number -Value 1 -Operator gt New-HTMLTableCondition -Name 'Types' -Operator like -ComparisonType string -Value 'static' New-HTMLTableCondition -Name 'Types' -Operator like -ComparisonType string -Value 'dynamic' } -BackgroundColor Rouge -Row -Color White New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Tombstoned' -BackgroundColor Orange -FailBackgroundColor LightGreen } } } } else { New-HTMLTable -DataTable $DNSByName -Filtering { New-HTMLTableCondition -Name 'Count' -ComparisonType number -Value 1 -BackgroundColor LightGreen New-HTMLTableCondition -Name 'Count' -ComparisonType number -Value 1 -Operator gt -BackgroundColor Orange New-HTMLTableConditionGroup -Logic AND { New-HTMLTableCondition -Name 'Count' -ComparisonType number -Value 1 -Operator gt New-HTMLTableCondition -Name 'Types' -Operator like -ComparisonType string -Value 'static' New-HTMLTableCondition -Name 'Types' -Operator like -ComparisonType string -Value 'dynamic' } -BackgroundColor Rouge -Row -Color White New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Tombstoned' -BackgroundColor Orange -FailBackgroundColor LightGreen } -DataStore JavaScript } } New-HTMLTab -Name 'DNS by IP' { New-HTMLTable -DataTable $DNSByIP -Filtering { New-HTMLTableCondition -Name 'Count' -ComparisonType number -Value 1 -BackgroundColor LightGreen New-HTMLTableCondition -Name 'Count' -ComparisonType number -Value 1 -Operator gt -BackgroundColor Orange New-HTMLTableConditionGroup -Logic AND { New-HTMLTableCondition -Name 'Count' -ComparisonType number -Value 1 -Operator gt New-HTMLTableCondition -Name 'Types' -Operator like -ComparisonType string -Value 'static' New-HTMLTableCondition -Name 'Types' -Operator like -ComparisonType string -Value 'dynamic' } -BackgroundColor Rouge -Row -Color White } -DataStore JavaScript } } -ShowHTML:(-not $HideHTML.IsPresent) -Online:$Online.IsPresent -TitleText "DNS Configuration" -FilePath $FilePath } function Show-WinADGroupCritical { <# .SYNOPSIS Command to gather nested group membership from default critical groups in the Active Directory. .DESCRIPTION Command to gather nested group membership from default critical groups in the Active Directory. This command will show data in table and diagrams in HTML format. .PARAMETER GroupName Group Name or Names to search for from provided list. If skipped all groups will be checked. .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 output from being displayed in browser after generation is done .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 SkipDiagram Skips diagram generation and only displays table. Useful if the diagram can't handle amount of data or if the diagrams are not nessecary. .PARAMETER Summary Adds additional tab with all groups together on two diagrams .EXAMPLE Show-WinADGroupCritical .NOTES General notes #> [alias('Show-WinADCriticalGroups')] [cmdletBinding()] param( [validateSet( "Domain Admins", "Cert Publishers", "Schema Admins", "Enterprise Admins", "DnsAdmins", "DnsAdmins2", "DnsUpdateProxy", "Group Policy Creator Owners", 'Protected Users', 'Key Admins', 'Enterprise Key Admins', 'Server Management', 'Organization Management', 'DHCP Users', 'DHCP Administrators', 'Administrators', 'Account Operators', 'Server Operators', 'Print Operators', 'Backup Operators', 'Replicators', 'Network Configuration Operations', 'Incoming Forest Trust Builders', 'Internet Information Services', 'Event Log Readers', 'Hyper-V Administrators', 'Remote Management Users' )] [string[]] $GroupName, [alias('ReportPath')][string] $FilePath, [ValidateSet('Default', 'Hierarchical', 'Both')][string] $HideAppliesTo = 'Both', [switch] $HideComputers, [switch] $HideUsers, [switch] $HideOther, [switch] $Online, [switch] $HideHTML, [switch] $DisableBuiltinConditions, [switch] $AdditionalStatistics, [switch] $SkipDiagram, [switch] $Summary ) $ForestInformation = Get-WinADForestDetails -Extended [Array] $ListGroups = foreach ($Domain in $ForestInformation.Domains) { $DomainSidValue = $ForestInformation.DomainsExtended[$Domain].DomainSID $PriviligedGroups = [ordered] @{ "Domain Admins" = "$DomainSidValue-512" "Cert Publishers" = "$DomainSidValue-517" "Schema Admins" = "$DomainSidValue-518" "Enterprise Admins" = "$DomainSidValue-519" "DnsAdmins" = "$DomainSidValue-1101" "DnsAdmins2" = "$DomainSidValue-1105" "DnsUpdateProxy" = "$DomainSidValue-1106" "Group Policy Creator Owners" = "$DomainSidValue-520" 'Protected Users' = "$DomainSidValue-525" 'Key Admins' = "$DomainSidValue-526" 'Enterprise Key Admins' = "$DomainSidValue-527" 'Server Management' = "$DomainSidValue-1125" 'Organization Management' = "$DomainSidValue-1117" 'DHCP Users' = "$DomainSidValue-2111" 'DHCP Administrators' = "$DomainSidValue-2112" 'Administrators' = "S-1-5-32-544" 'Account Operators' = "S-1-5-32-548" 'Server Operators' = "S-1-5-32-549" 'Print Operators' = "S-1-5-32-550" 'Backup Operators' = "S-1-5-32-551" 'Replicators' = "S-1-5-32-552" 'Network Configuration Operations' = "S-1-5-32-556" 'Incoming Forest Trust Builders' = "S-1-5-32-557" 'Internet Information Services' = "S-1-5-32-568" 'Event Log Readers' = "S-1-5-32-573" 'Hyper-V Administrators' = "S-1-5-32-578" 'Remote Management Users' = "S-1-5-32-580" } foreach ($Group in $PriviligedGroups.Keys) { $SearchName = $PriviligedGroups[$Group] if ($GroupName -and $Group -notin $GroupName) { continue } $GroupInformation = (Get-ADGroup -Filter "SID -eq '$SearchName'" -Server $ForestInformation['QueryServers'][$Domain].HostName[0] -ErrorAction SilentlyContinue).DistinguishedName if ($GroupInformation) { $GroupInformation } } } if ($ListGroups.Count -gt 0) { Show-WinADGroupMember -Identity $ListGroups -HideHTML:$HideHTML.IsPresent -FilePath $FilePath -DisableBuiltinConditions:$DisableBuiltinConditions.IsPresent -Online:$Online.IsPresent -HideUsers:$HideUsers.IsPresent -HideComputers:$HideComputers.IsPresent -AdditionalStatistics:$AdditionalStatistics.IsPresent -Summary:$Summary.IsPresent -SkipDiagram:$SkipDiagram.IsPresent } else { Write-Warning -Message "Show-WinADGroupCritical - Requested group(s) not found." } } function Show-WinADGroupMember { <# .SYNOPSIS Command to gather nested group membership from one or more groups and display in table with two diagrams .DESCRIPTION Command to gather nested group membership from one or more groups and display in table with two diagrams This command will show data in table and diagrams in HTML format. .PARAMETER Identity Group Name or Names 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 output from being displayed in browser after generation is done .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 SkipDiagram Skips diagram generation and only displays table. Useful if the diagram can't handle amount of data or if the diagrams are not nessecary. .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, [switch] $SkipDiagram, [Parameter(ParameterSetName = 'Default')][switch] $Summary, [Parameter(ParameterSetName = 'SummaryOnly')][switch] $SummaryOnly ) $Script:Reporting = [ordered] @{} $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Show-WinADGroupMember' -RepositoryOwner 'evotecit' -RepositoryName 'ADEssentials' $VisualizeOnly = $false if ($FilePath -eq '') { $FilePath = Get-FileName -Extension 'html' -Temporary } $GroupsList = [System.Collections.Generic.List[object]]::new() if ($Identity.Count -gt 0) { New-HTML -TitleText "Visual Group Membership" { 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 } } New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLTableOption -DataStore JavaScript -BoolAsString 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) { if ($null -eq $Group) { continue } 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 } Write-Verbose "Show-WinADGroupMember - processing HTML generation for $Group group" if (-not $SummaryOnly) { if ($ADGroup) { # Means group returned something $GroupName = $ADGroup[0].GroupName $NetBIOSName = Convert-DomainFqdnToNetBIOS -DomainName $ADGroup[0].DomainName $FullName = "$NetBIOSName\$GroupName" } else { # Means group returned nothing, probably wrong request, but we still need to show something $GroupName = $Group $FullName = $Group } $DataStoreID = -join ('table', (Get-RandomStringName -Size 10 -ToLower)) $DataTableID = -join ('table', (Get-RandomStringName -Size 10 -ToLower)) New-HTMLTab -TabName $FullName { Write-Verbose -Message "Show-WinADGroupMember - processing HTML generation for $Group group - Table" $SectionInformation = 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 } } } if (-not $SkipDiagram.IsPresent) { New-HTMLTab -TabName 'Information' { $SectionInformation } } else { $SectionInformation } if (-not $SkipDiagram.IsPresent) { Write-Verbose -Message "Show-WinADGroupMember - processing HTML generation for $Group group - Diagram" 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 } } Write-Verbose -Message "Show-WinADGroupMember - processing HTML generation for $Group group - Diagram Hierarchy" 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 (-not $SkipDiagram.IsPresent -and ($Summary -or $SummaryOnly)) { Write-Verbose "Show-WinADGroupMember - processing HTML generation for Summary" 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 } } } } Write-Verbose -Message "Show-WinADGroupMember - saving HTML report" } -Online:$Online -FilePath $FilePath -ShowHTML:(-not $HideHTML) Write-Verbose -Message "Show-WinADGroupMember - HTML report saved to $FilePath" } else { Write-Warning -Message "Show-WinADGroupMember - Error processing Identity, as it's empty." } } function Show-WinADGroupMemberOf { <# .SYNOPSIS Command to gather group membership that the user is member of displaying information in table and diagrams. .DESCRIPTION Command to gather group membership that the user is member of displaying information in table and diagrams. .PARAMETER Identity User or Computer object to get group membership 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 Summary Adds additional tab with all groups together on two diagrams .PARAMETER SummaryOnly Adds one tab with all groups together on two diagrams .PARAMETER Online Forces use of online CDN for JavaScript/CSS which makes the file smaller. Default - use offline. .PARAMETER HideHTML Prevents HTML output from being displayed in browser after generation is done .PARAMETER DisableBuiltinConditions Disables table coloring allowing user to define it's own conditions .PARAMETER SkipDiagram Skips diagram generation and only displays table. Useful if the diagram can't handle amount of data or if the diagrams are not nessecary. .EXAMPLE Show-WinADGroupMemberOf -Identity 'przemyslaw.klys' -Verbose -Summary .EXAMPLE Show-WinADGroupMemberOf -Identity 'przemyslaw.klys', 'adm.pklys' -Summary .NOTES General notes #> [alias('Show-ADGroupMemberOf')] [cmdletBinding(DefaultParameterSetName = 'Default')] param( [Parameter(Position = 1)][scriptblock] $Conditions, [parameter(Position = 0, Mandatory)][string[]] $Identity, [string] $FilePath, [Parameter(ParameterSetName = 'Default')][switch] $Summary, [Parameter(ParameterSetName = 'SummaryOnly')][switch] $SummaryOnly, [switch] $Online, [switch] $HideHTML, [switch] $DisableBuiltinConditions, [switch] $SkipDiagram ) $HideAppliesTo = 'Both' $Script:Reporting = [ordered] @{} $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Show-WinADGroupMemberOf' -RepositoryOwner 'evotecit' -RepositoryName 'ADEssentials' if ($FilePath -eq '') { $FilePath = Get-FileName -Extension 'html' -Temporary } $GroupsList = [System.Collections.Generic.List[object]]::new() New-HTML -TitleText "Visual Object MemberOf" { 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 } } New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLTableOption -DataStore JavaScript -BoolAsString New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey foreach ($ADObject in $Identity) { if ($null -eq $ADObject) { continue } try { Write-Verbose "Show-WinADObjectMember - requesting $ADObject memberof 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 } Write-Verbose -Message "Show-WinADGroupMemberOf - Processing HTML generation for $ADObject" 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 { Write-Verbose -Message "Show-WinADGroupMemberOf - Processing HTML generation for $ObjectName - Table" $DataSection = 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 CircularDirect -Operator eq -Row New-TableCondition -BackgroundColor CoralRed -Color White -ComparisonType bool -Value $true -Name CircularIndirect -Operator eq -Row } if ($Conditions) { & $Conditions } } } if ($SkipDiagram.IsPresent) { $DataSection } else { New-HTMLTab -TabName 'Information' { $DataSection } Write-Verbose -Message "Show-WinADGroupMemberOf - Processing HTML generation for $ObjectName - Diagram" 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 } } Write-Verbose -Message "Show-WinADGroupMemberOf - Processing HTML generation for $ObjectName - Diagram Hierarchy" 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 (-not $SkipDiagram.IsPresent -and ($Summary -or $SummaryOnly)) { Write-Verbose -Message "Show-WinADGroupMemberOf - Processing HTML generation for Summary" 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 } } } } Write-Verbose -Message "Show-WinADGroupMemberOf - saving HTML report" } -Online:$Online -FilePath $FilePath -ShowHTML:(-not $HideHTML) } function Show-WinADKerberosAccount { [CmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [switch] $Online, [switch] $HideHTML, [string] $FilePath, [switch] $PassThru ) $Today = Get-Date $Script:Reporting = [ordered] @{} $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Invoke-ADEssentials' -RepositoryOwner 'evotecit' -RepositoryName 'ADEssentials' $AccountData = Get-WinADKerberosAccount -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -IncludeCriticalAccounts Write-Verbose -Message "Show-WinADKerberosAccount - Building HTML report based on delivered data" New-HTML -Author 'PrzemysÅ‚aw KÅ‚ys' -TitleText 'Kerberos Reporting' { New-HTMLTabStyle -BorderRadius 0px -TextTransform lowercase -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 } } foreach ($Domain in $AccountData.Data.Keys) { New-HTMLTab -Name $Domain { New-HTMLPanel { New-HTMLTable -DataTable $AccountData['Data'][$Domain].Values.FullInformation -Filtering -DataStore JavaScript -ScrollX { $newHTMLTableConditionSplat = @{ Name = 'PasswordLastSetDays' ComparisonType = 'number' Operator = 'le' Value = 180 BackgroundColor = 'LimeGreen' FailBackgroundColor = 'Salmon' HighlightHeaders = 'PasswordLastSetDays', 'PasswordLastSet' } New-HTMLTableCondition @newHTMLTableConditionSplat } } New-HTMLTabPanel { foreach ($Account in $AccountData['Data'][$Domain].Values) { $DomainControllers = $Account.DomainControllers $GlobalCatalogs = $Account.GlobalCatalogs $CountMatched = 0 $CountNotMatched = 0 $CountTotal = 0 $NewestPassword = $DomainControllers.Values.PasswordLastSet | Sort-Object -Descending | Select-Object -First 1 foreach ($Password in $DomainControllers.Values.PasswordLastSet) { if ($Password -eq $NewestPassword) { $CountMatched++ } else { $CountNotMatched++ } $CountTotal++ } if ($NewestPassword) { $TimeSinceLastChange = ($Today) - $NewestPassword } else { $TimeSinceLastChange = $null } $CountMatchedGC = 0 $CountNotMatchedGC = 0 $CountTotalGC = 0 $NewestPasswordGC = $GlobalCatalogs.Values.PasswordLastSet | Sort-Object -Descending | Select-Object -First 1 foreach ($Password in $GlobalCatalogs.Values.PasswordLastSet) { if ($Password -eq $NewestPasswordGC) { $CountMatchedGC++ } else { $CountNotMatchedGC++ } $CountTotalGC++ } if ($NewestPasswordGC) { $TimeSinceLastChangeGC = ($Today) - $NewestPasswordGC } else { $TimeSinceLastChangeGC = $null } New-HTMLTab -Name $Account.FullInformation.SamAccountName { New-HTMLSection -Invisible { # DC Status New-HTMLSection -Invisible { New-HTMLPanel -Invisible { New-HTMLStatus { $Percentage = "$([math]::Round(($CountMatched / $CountTotal) * 100))%" if ($Percentage -eq '100%') { $BackgroundColor = '#0ef49b' $Icon = 'Good' } elseif ($Percentage -ge '70%') { $BackgroundColor = '#d2dc69' $Icon = 'Bad' } elseif ($Percentage -ge '30%') { $BackgroundColor = '#faa04b' $Icon = 'Bad' } elseif ($Percentage -ge '10%') { $BackgroundColor = '#ff9035' $Icon = 'Bad' } elseif ($Percentage -ge '0%') { $BackgroundColor = '#ff5a64' $Icon = 'Dead' } if ($Icon -eq 'Dead') { $IconType = '☠' } elseif ($Icon -eq 'Bad') { $IconType = '☹' } elseif ($Icon -eq 'Good') { $IconType = '✔' } New-HTMLStatusItem -Name 'Domain Controller' -Status "Synchronized $CountMatched/$CountTotal ($Percentage)" -BackgroundColor $BackgroundColor -IconHex $IconType $newHTMLToastSplat = @{ TextHeader = 'Kerberos password date' Text = "Password set on: $NewestPassword (Days: $($TimeSinceLastChange.Days), Hours: $($TimeSinceLastChange.Hours), Minutes: $($TimeSinceLastChange.Minutes))" BarColorLeft = 'AirForceBlue' IconSolid = 'info-circle' IconColor = 'AirForceBlue' } if ($TimeSinceLastChange.Days -ge 180) { $newHTMLToastSplat['BarColorLeft'] = 'Salmon' $newHTMLToastSplat['IconSolid'] = 'exclamation-triangle' $newHTMLToastSplat['IconColor'] = 'Salmon' $newHTMLToastSplat['TextHeader'] = 'Kerberos password date (outdated)' } New-HTMLToast @newHTMLToastSplat } } } # GC Status New-HTMLSection -Invisible { New-HTMLStatus { $Percentage = "$([math]::Round(($CountMatchedGC / $CountTotalGC) * 100))%" if ($Percentage -eq '100%') { $BackgroundColor = '#0ef49b' $Icon = 'Good' } elseif ($Percentage -ge '70%') { $BackgroundColor = '#d2dc69' $Icon = 'Bad' } elseif ($Percentage -ge '30%') { $BackgroundColor = '#faa04b' $Icon = 'Bad' } elseif ($Percentage -ge '10%') { $BackgroundColor = '#ff9035' $Icon = 'Bad' } elseif ($Percentage -ge '0%') { $BackgroundColor = '#ff5a64' $Icon = 'Dead' } if ($Icon -eq 'Dead') { $IconType = '☠' } elseif ($Icon -eq 'Bad') { $IconType = '☹' } elseif ($Icon -eq 'Good') { $IconType = '✔' } New-HTMLStatusItem -Name 'Global Catalogs' -Status "Synchronized $CountMatchedGC/$CountTotalGC ($Percentage)" -BackgroundColor $BackgroundColor -IconHex $IconType $newHTMLToastSplat = @{ TextHeader = 'Kerberos password date' Text = "Password set on: $NewestPasswordGC (Days: $($TimeSinceLastChangeGC.Days), Hours: $($TimeSinceLastChangeGC.Hours), Minutes: $($TimeSinceLastChangeGC.Minutes))" BarColorLeft = 'AirForceBlue' IconSolid = 'info-circle' IconColor = 'AirForceBlue' } if ($TimeSinceLastChange.Days -ge 180) { $newHTMLToastSplat['BarColorLeft'] = 'Salmon' $newHTMLToastSplat['IconSolid'] = 'exclamation-triangle' $newHTMLToastSplat['IconColor'] = 'Salmon' $newHTMLToastSplat['TextHeader'] = 'Kerberos password date (outdated)' } New-HTMLToast @newHTMLToastSplat } } } #$DataAccount = $Account.FullInformation New-HTMLSection -HeaderText "Domain Controllers for '$($Account.FullInformation.SamAccountName)'" { New-HTMLTable -DataTable $DomainControllers.Values { New-HTMLTableCondition -Name 'Status' -Operator eq -Value 'OK' -BackgroundColor '#0ef49b' -FailBackgroundColor '#ff5a64' } -Filtering -DataStore JavaScript } New-HTMLSection -HeaderText "Global Catalogs for account '$($Account.FullInformation.SamAccountName)'" { New-HTMLTable -DataTable $GlobalCatalogs.Values { New-HTMLTableCondition -Name 'Status' -Operator eq -Value 'OK' -BackgroundColor '#0ef49b' -FailBackgroundColor '#ff5a64' } -Filtering -DataStore JavaScript } } } } $KerberosAccount = $AccountData['Data'][$Domain]['krbtgt'].FullInformation $NewestPassword = $KerberosAccount.PasswordLastSetDays New-HTMLSection -HeaderText "Critical Accounts for domain '$Domain'" { New-HTMLContainer { New-HTMLPanel { New-HTMLText -Text "Critical accounts that should have their password changed after every kerberos password change." New-HTMLList { New-HTMLListItem -Text 'Domain Admins' New-HTMLListItem -Text 'Enterprise Admins' } } New-HTMLPanel { New-HTMLTable -DataTable $AccountData['CriticalAccounts'][$Domain] { if ($null -ne $NewestPassword) { New-HTMLTableCondition -Name 'PasswordLastSetDays' -Operator le -Value $NewestPassword -ComparisonType number -BackgroundColor MintGreen -FailBackgroundColor Salmon -HighlightHeaders PasswordLastSetDays, PasswordLastSet } } -Filtering -DataStore JavaScript -ScrollX } } } } } } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath if ($PassThru) { $AccountData } Write-Verbose -Message "Show-WinADKerberosAccount - HTML Report generated" } function Show-WinADObjectDifference { [CmdletBinding()] param( [Array] $Identity, [switch] $GlobalCatalog, [string[]] $Properties, [string] $FilePath, [switch] $HideHTML ) $OutputValue = Find-WinADObjectDifference -Identity $Identity -GlobalCatalog:$GlobalCatalog.IsPresent -Properties $Properties Write-Verbose -Message "Show-WinADObjectDifference - Generating HTML" New-HTML { New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ", " -ArrayJoin New-HTMLTab -Name 'Summary' { New-HTMLTable -DataTable $OutputValue.ListSummary -Filtering -DataStore JavaScript -ScrollX { New-HTMLTableCondition -Name 'DifferentServersCount' -Operator eq -ComparisonType number -Value 0 -BackgroundColor LimeGreen -FailBackgroundColor Salmon -HighlightHeaders 'DifferentServersCount', 'DifferentServers', 'DifferentProperties' New-HTMLTableCondition -Name 'SameServersCount' -Operator eq -ComparisonType number -Value 0 -BackgroundColor Salmon -FailBackgroundColor LimeGreen -HighlightHeaders 'SameServersCount', 'SameServers', 'SameProperties' } } New-HTMLTab -Name 'Details per property' { New-HTMLTable -DataTable $OutputValue.ListDetails -Filtering -DataStore JavaScript -ScrollX -AllProperties } New-HTMLTab -Name 'Details per server' { New-HTMLTable -DataTable $OutputValue.ListDetailsReversed -Filtering -DataStore JavaScript -ScrollX } New-HTMLTab -Name 'Detailed Differences' { New-HTMLTable -DataTable $OutputValue.List -Filtering -DataStore JavaScript -ScrollX } } -ShowHTML:(-not $HideHTML.IsPresent) -FilePath $FilePath Write-Verbose -Message "Show-WinADObjectDifference - Generating HTML - Done" } function Show-WinADOrganization { [cmdletBinding()] param( [ScriptBlock] $Conditions, [string] $FilePath ) $CachedOU = [ordered] @{} $ForestInformation = Get-WinADForestDetails $Script:OrganiazationalUnits = @() #$Organization = Get-WinADOrganization New-HTML -TitleText "Visual Active Directory Organization" { New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLTableOption -DataStore HTML New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey New-HTMLTabPanel { New-HTMLTab -TabName 'Standard' { New-HTMLSection -HeaderText 'Organization Diagram' { New-HTMLDiagram -Height 'calc(50vh)' { New-DiagramEvent -ID 'DT-StandardOrg' -ColumnID 3 New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion #foreach ($OU in $Organization.Keys) { #Add-Node -Name $OU -Organization $Organization #New-DiagramNode -Label $OU #} foreach ($Domain in $ForestInformation.Domains) { New-DiagramNode -Label $Domain -Id $Domain -Image 'https://cdn-icons-png.flaticon.com/512/6329/6329785.png' $Script:OrganiazationalUnits = Get-ADOrganizationalUnit -Filter "*" -Server $ForestInformation['QueryServers'][$Domain].HostName[0] -Properties DistinguishedName, CanonicalName foreach ($OU in $OrganiazationalUnits) { New-DiagramNode -Id $OU.DistinguishedName -Label $OU.Name -Image 'https://cdn-icons-png.flaticon.com/512/3767/3767084.png' [Array] $SubOU = ConvertFrom-DistinguishedName -DistinguishedName $OU.DistinguishedName -ToMultipleOrganizationalUnit if ($SubOU.Count -gt 0) { foreach ($Sub in $SubOU[0]) { $Name = ConvertFrom-DistinguishedName -DistinguishedName $Sub -ToLastName New-DiagramNode -Id $Sub -Label $Name -Image 'https://cdn-icons-png.flaticon.com/512/3767/3767084.png' New-DiagramEdge -From $OU.DistinguishedName -To $Sub -Color Blue -ArrowsToEnabled -Dashes } } else { New-DiagramEdge -From $Domain -To $OU.DistinguishedName -Color Blue -ArrowsToEnabled -Dashes } <# $NameSplit = $OU.canonicalName.Split("/") $CurrentLevel = $CachedOU[$Domain] foreach ($N in $NameSplit) { if ($N -ne $Domain) { if (-not $CurrentLevel[$N]) { $CurrentLevel[$N] = [ordered] @{} } else { $CurrentLevel = $CurrentLevel[$N] } } } #> } <# foreach ($OU in $OrganiazationalUnits) { [Array] $SubOU = ConvertFrom-DistinguishedName -DistinguishedName $OU.DistinguishedName -ToMultipleOrganizationalUnit -IncludeParent | Select-Object -Last 1 New-DiagramLink -From $OU.DistinguishedName -To $O } #> } #$CachedOU foreach ($Trust in $ADTrusts) { #New-DiagramNode -Label $Trust.'TrustSource' -IconSolid audio-description #-IconColor LightSteelBlue #New-DiagramNode -Label $Trust.'TrustTarget' -IconSolid audio-description #-IconColor LightSteelBlue $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-HTMLTab -TabName 'Hierarchical' { New-HTMLSection -HeaderText 'Organization Diagram' { New-HTMLDiagram -Height 'calc(50vh)' { New-DiagramOptionsLayout -HierarchicalEnabled $true New-DiagramEvent -ID 'DT-StandardOrg' -ColumnID 3 New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion #foreach ($OU in $Organization.Keys) { #Add-Node -Name $OU -Organization $Organization #New-DiagramNode -Label $OU #} foreach ($Domain in $ForestInformation.Domains) { New-DiagramNode -Label $Domain -Id $Domain -Image 'https://cdn-icons-png.flaticon.com/512/6329/6329785.png' $Script:OrganiazationalUnits = Get-ADOrganizationalUnit -Filter "*" -Server $ForestInformation['QueryServers'][$Domain].HostName[0] -Properties DistinguishedName, CanonicalName foreach ($OU in $OrganiazationalUnits) { New-DiagramNode -Id $OU.DistinguishedName -Label $OU.Name -Image 'https://cdn-icons-png.flaticon.com/512/3767/3767084.png' [Array] $SubOU = ConvertFrom-DistinguishedName -DistinguishedName $OU.DistinguishedName -ToMultipleOrganizationalUnit if ($SubOU.Count -gt 0) { foreach ($Sub in $SubOU[0]) { $Name = ConvertFrom-DistinguishedName -DistinguishedName $Sub -ToLastName New-DiagramNode -Id $Sub -Label $Name -Image 'https://cdn-icons-png.flaticon.com/512/3767/3767084.png' New-DiagramEdge -From $OU.DistinguishedName -To $Sub } } else { New-DiagramEdge -From $Domain -To $OU.DistinguishedName } <# $NameSplit = $OU.canonicalName.Split("/") $CurrentLevel = $CachedOU[$Domain] foreach ($N in $NameSplit) { if ($N -ne $Domain) { if (-not $CurrentLevel[$N]) { $CurrentLevel[$N] = [ordered] @{} } else { $CurrentLevel = $CurrentLevel[$N] } } } #> } <# foreach ($OU in $OrganiazationalUnits) { [Array] $SubOU = ConvertFrom-DistinguishedName -DistinguishedName $OU.DistinguishedName -ToMultipleOrganizationalUnit -IncludeParent | Select-Object -Last 1 New-DiagramLink -From $OU.DistinguishedName -To $O } #> } #$CachedOU foreach ($Trust in $ADTrusts) { #New-DiagramNode -Label $Trust.'TrustSource' -IconSolid audio-description #-IconColor LightSteelBlue #New-DiagramNode -Label $Trust.'TrustTarget' -IconSolid audio-description #-IconColor LightSteelBlue $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 $Script:OrganiazationalUnits -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-StandardOrg' } } -ShowHTML -FilePath $FilePath -Online } function Show-WinADSites { [cmdletBinding()] param( [ScriptBlock] $Conditions, [string] $FilePath ) $CacheReplication = @{} $Sites = Get-WinADForestSites $Replication = Get-WinADForestReplication foreach ($Rep in $Replication) { $CacheReplication["$($Rep.Server)$($Rep.ServerPartner)"] = $Rep } New-HTML -TitleText "Visual Active Directory Organization" { New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLTableOption -DataStore HTML New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey New-HTMLTabPanel { New-HTMLTab -TabName 'Standard' { New-HTMLSection -HeaderText 'Organization Diagram' { New-HTMLDiagram -Height 'calc(50vh)' { New-DiagramEvent -ID 'DT-StandardSites' -ColumnID 0 New-DiagramOptionsPhysics -RepulsionNodeDistance 150 -Solver repulsion foreach ($Site in $Sites) { New-DiagramNode -Id $Site.DistinguishedName -Label $Site.Name -Image 'https://cdn-icons-png.flaticon.com/512/1104/1104991.png' foreach ($Subnet in $Site.Subnets) { New-DiagramNode -Id $Subnet -Label $Subnet -Image 'https://cdn-icons-png.flaticon.com/512/1674/1674968.png' New-DiagramEdge -From $Subnet -To $Site.DistinguishedName } foreach ($DC in $Site.DomainControllers) { New-DiagramNode -Id $DC -Label $DC -Image 'https://cdn-icons-png.flaticon.com/512/1383/1383395.png' New-DiagramEdge -From $DC -To $Site.DistinguishedName } } foreach ($R in $CacheReplication.Values) { if ($R.ConsecutiveReplicationFailures -gt 0) { $Color = 'CoralRed' } else { $Color = 'MediumSeaGreen' } New-DiagramEdge -From $R.Server -To $R.ServerPartner -Color $Color -ArrowsToEnabled -ColorOpacity 0.5 } } } } } New-HTMLSection -Title "Information about Sites" { New-HTMLTable -DataTable $Sites -Filtering { if (-not $DisableBuiltinConditions) { New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType number -Value 0 -Name SubnetsCount -Operator gt New-TableCondition -BackgroundColor CoralRed -ComparisonType number -Value 0 -Name SubnetsCount -Operator eq } if ($Conditions) { & $Conditions } } -DataTableID 'DT-StandardSites' -DataStore JavaScript } New-HTMLTable -DataTable $Replication -Filtering { if (-not $DisableBuiltinConditions) { New-TableCondition -BackgroundColor MediumSeaGreen -ComparisonType number -Value 0 -Name SubnetsCount -Operator gt New-TableCondition -BackgroundColor CoralRed -ComparisonType number -Value 0 -Name SubnetsCount -Operator eq } if ($Conditions) { & $Conditions } } -DataTableID 'DT-StandardSites1' -DataStore JavaScript } -ShowHTML -FilePath $FilePath -Online } 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 #$Messages = $($ADTrusts = Get-WinADTrust -Recursive:$Recursive) 4>&1 3>&1 2>&1 #$Messages += Write-Verbose "Show-WinADTrust - Found $($ADTrusts.Count) trusts" 4>&1 $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-DiagramEvent -ID 'DT-TrustsInformation' -ColumnID 0 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 #-IconColor LightSteelBlue New-DiagramNode -Label $Trust.'TrustTarget' -IconSolid audio-description #-IconColor LightSteelBlue $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' } } # Lets try to sort it into source domain per tab $TrustCache = [ordered]@{} foreach ($Trust in $ADTrusts) { #$Messages += Write-Verbose "Show-WinADTrust - Processing $($Trust.TrustSource) to $($Trust.TrustTarget)" 4>&1 Write-Verbose "Show-WinADTrust - Processing $($Trust.TrustSource) to $($Trust.TrustTarget)" if (-not $TrustCache[$Trust.TrustSource]) { #$Messages += Write-Verbose "Show-WinADTrust - Creating cache for $($Trust.TrustSource)" 4>&1 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 } } } } } } } } } #New-HTMLTab -TabName "Logs" { # New-HTMLTable -DataTable ($Messages.Message) #} } -Online:$Online -FilePath $FilePath -ShowHTML:(-not $HideHTML) if ($PassThru) { $Script:ADTrusts } } function Show-WinADUserSecurity { [cmdletBinding()] param( [string[]] $Identity ) New-HTML { foreach ($I in $Identity) { $User = Get-WinADObject -Identity $I $ACL = Get-ADACL -ADObject $User.Distinguishedname $Objects = [ordered] @{} $GroupsList = foreach ($A in $ACL) { $Objects[$A.Principal] = Get-WinADObject -Identity $A.Principal if ($Objects[$A.Principal].ObjectClass -eq 'group') { $Objects[$A.Principal].Distinguishedname } } $Groups = $Objects.Values | Where-Object { $_.ObjectClass -eq 'group' } | Sort-Object -Property Distinguishedname $GroupsList = foreach ($G in $Groups) { Get-WinADGroupMember -Identity $G.Distinguishedname -AddSelf } New-HTMLTab -Name "$($User.DomainName)\$($User.SamAccountName)" { New-HTMLSection -Invisible { New-HTMLPanel { New-HTMLTable -DataTable $User } New-HTMLPanel { New-HTMLTable -Filtering -DataTable $ACL -IncludeProperty Principal, AccessControlType, ActiveDirectoryRights, ObjectTypeName, InheritedObjectTypeName, InhertitanceType, IsInherited } } New-HTMLSection -Invisible { New-HTMLTable -Filtering -DataTable $Objects.Keys } $HideAppliesTo = 'Default' New-HTMLTabPanel { 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 -ShowHTML } function Sync-WinADDomainController { [alias('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++) { # Correct wrong line breaks if ($Result[$Line] -match '^\s{9}.{25} (\S+) (\S+) test$') { $Result[$Line] = $Result[$Line] + ' ' + $Result[$Line + 2].Trim() } # Verify test start line if ($Result[$Line] -match '^\s{6}Starting test: \S+$') { $LineStart = $Line } # Verify test end line if ($Result[$Line] -match '^\s{9}.{25} (\S+) (\S+) test (\S+)$') { $DiagnosticResult = [PSCustomObject] @{ ComputerName = $Computer #Domain = $Domain 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 } } } } #Test-ADRolesAvailability 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') { # Bad $LinksUsingNotificationsUnnessecary.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } else { # Good $LinksUsingNotificationsWhichIsOk.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } } else { if ($Link.Options -contains 'UseNotify') { # Good $LinksUsingNotifications.Add("$($Link.ServerFrom) to $($Link.ServerTo)") } else { # Bad $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 } #$Summary["$($S.HostName)"] = @{ } foreach ($_ in $S.OperationMasterRoles) { #$Summary["$_"] = $S.HostName [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 .PARAMETER Identity User to search for using LDAP query by objectGUID, objectSID, SamAccountName, UserPrincipalName, Name or DistinguishedName .PARAMETER Extended Returns additional information about LDAP Server including full objects .PARAMETER SkipCheckGC Skips querying GC ports .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, [Parameter(ParameterSetName = 'Computer')] [Parameter(ParameterSetName = 'Forest')] [string] $Identity, [Parameter(ParameterSetName = 'Computer')] [Parameter(ParameterSetName = 'Forest')] [switch] $Extended, [Parameter(ParameterSetName = 'Computer')] [switch] $SkipCheckGC ) 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) { if ($Computer -match '^(\d+\.){3}\d+$') { try { $ServerName = [System.Net.Dns]::GetHostByAddress($Computer).HostName } catch { Write-Warning "Test-LDAP - Unable to resolve $Computer. $($_.Exception.Message)" $ServerName = $Computer } } else { try { $ServerName = [System.Net.Dns]::GetHostByName($Computer).HostName } catch { Write-Warning "Test-LDAP - Unable to resolve $Computer. $($_.Exception.Message)" $ServerName = $Computer } } Write-Verbose "Test-LDAP - Processing $Computer / $ServerName" $testLdapServerSplat = @{ ServerName = $ServerName Computer = $Computer GCPortLDAP = $GCPortLDAP GCPortLDAPSSL = $GCPortLDAPSSL PortLDAP = $PortLDAP PortLDAPS = $PortLDAPS VerifyCertificate = $VerifyCertificate.IsPresent Identity = $Identity SkipCheckGC = $SkipCheckGC } if ($PSBoundParameters.ContainsKey('Credential')) { $testLdapServerSplat.Credential = $Credential } Test-LdapServer @testLdapServerSplat } } else { foreach ($Computer in $ForestInformation.ForestDomainControllers) { Write-Verbose "Test-LDAP - Processing $($Computer.HostName)" $testLdapServerSplat = @{ ServerName = $($Computer.HostName) Computer = $Computer.HostName Advanced = $Computer GCPortLDAP = $GCPortLDAP GCPortLDAPSSL = $GCPortLDAPSSL PortLDAP = $PortLDAP PortLDAPS = $PortLDAPS VerifyCertificate = $VerifyCertificate.IsPresent Identity = $Identity } if ($PSBoundParameters.ContainsKey('Credential')) { $testLdapServerSplat.Credential = $Credential } Test-LdapServer @testLdapServerSplat } } } } function Test-WinADDNSResolving { <# .SYNOPSIS Test DNS resolving for specific DNS record type across all Domain Controllers in the forest. .DESCRIPTION Test DNS resolving for specific DNS record type across all Domain Controllers in the forest. .PARAMETER Name Name of the DNS record to resolve .PARAMETER Type Type of the DNS record to resolve .PARAMETER Forest Forest name to use for resolving. If not given it will use current forest. .PARAMETER ExcludeDomains Exclude specific domains from test .PARAMETER ExcludeDomainControllers Exclude specific domain controllers from test .PARAMETER IncludeDomains Include specific domains in test .PARAMETER IncludeDomainControllers Include specific domain controllers in test .PARAMETER SkipRODC Skip Read Only Domain Controllers when querying for information .PARAMETER NotDNSOnly Do not use DNS only switch for resolving DNS names .EXAMPLE @( Test-WinADDNSResolving -Name "PILAFU085.ad.evotec.xyz" -Type "A" -Verbose -IncludeDomains 'ad.evotec.xyz' Test-WinADDNSResolving -Name "15.241.168.192.in-addr.arpa" -Type "PTR" -Verbose Test-WinADDNSResolving -Name "192.168.241.15" -Type "PTR" -Verbose Test-WinADDNSResolving -Name "Evo-win.ad.evotec.xyz" -Type "CNAME" -Verbose Test-WinADDNSResolving -Name "test.domain.pl" -Type "MX" -Verbose ) | Format-Table .OUTPUTS Name Type DC Resolving Identical ErrorMessage ResolvedName ResolvedData ---- ---- -- --------- --------- ------------ ------------ ------------ PILAFU085.ad.evotec.xyz A AD2.ad.evotec.xyz True True PILAFU085.ad.evotec.xyz 10.104.65.85 PILAFU085.ad.evotec.xyz A AD1.ad.evotec.xyz True True PILAFU085.ad.evotec.xyz 10.104.65.85 PILAFU085.ad.evotec.xyz A AD0.ad.evotec.xyz True True PILAFU085.ad.evotec.xyz 10.104.65.85 15.241.168.192.in-addr.arpa PTR AD2.ad.evotec.xyz True True 15.241.168.192.in-addr.arpa ADConnect.ad.evotec.xyz 15.241.168.192.in-addr.arpa PTR AD1.ad.evotec.xyz True True 15.241.168.192.in-addr.arpa ADConnect.ad.evotec.xyz 15.241.168.192.in-addr.arpa PTR AD0.ad.evotec.xyz True True 15.241.168.192.in-addr.arpa ADConnect.ad.evotec.xyz 15.241.168.192.in-addr.arpa PTR DC1.ad.evotec.pl True True 15.241.168.192.in-addr.arpa ADConnect.ad.evotec.xyz 15.241.168.192.in-addr.arpa PTR ADRODC.ad.evotec.pl True True 15.241.168.192.in-addr.arpa ADConnect.ad.evotec.xyz 192.168.241.15 PTR AD2.ad.evotec.xyz True True 15.241.168.192.in-addr.arpa ADConnect.ad.evotec.xyz 192.168.241.15 PTR AD1.ad.evotec.xyz True True 15.241.168.192.in-addr.arpa ADConnect.ad.evotec.xyz 192.168.241.15 PTR AD0.ad.evotec.xyz True True 15.241.168.192.in-addr.arpa ADConnect.ad.evotec.xyz 192.168.241.15 PTR DC1.ad.evotec.pl True True 15.241.168.192.in-addr.arpa ADConnect.ad.evotec.xyz 192.168.241.15 PTR ADRODC.ad.evotec.pl True True 15.241.168.192.in-addr.arpa ADConnect.ad.evotec.xyz Evo-win.ad.evotec.xyz CNAME AD2.ad.evotec.xyz True True Evo-win.ad.evotec.xyz EVOWIN.ad.evotec.xyz Evo-win.ad.evotec.xyz CNAME AD1.ad.evotec.xyz True True Evo-win.ad.evotec.xyz EVOWIN.ad.evotec.xyz Evo-win.ad.evotec.xyz CNAME AD0.ad.evotec.xyz True True Evo-win.ad.evotec.xyz EVOWIN.ad.evotec.xyz Evo-win.ad.evotec.xyz CNAME DC1.ad.evotec.pl True True Evo-win.ad.evotec.xyz EVOWIN.ad.evotec.xyz Evo-win.ad.evotec.xyz CNAME ADRODC.ad.evotec.pl True True Evo-win.ad.evotec.xyz EVOWIN.ad.evotec.xyz test.domain.pl MX AD2.ad.evotec.xyz True True test.domain.pl 10 office.com test.domain.pl MX AD1.ad.evotec.xyz True True test.domain.pl 10 office.com test.domain.pl MX AD0.ad.evotec.xyz True True test.domain.pl 10 office.com test.domain.pl MX DC1.ad.evotec.pl True True test.domain.pl 10 office.com test.domain.pl MX ADRODC.ad.evotec.pl True True test.domain.pl 10 office.com .NOTES General notes #> [CmdletBinding()] param( [Parameter(Mandatory)][string[]] $Name, [Parameter(Mandatory)][ValidateSet('PTR', 'A', 'AAAA', 'MX', 'CNAME', 'SRV')][string] $Type, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [switch] $NotDNSOnly ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation -Extended $StatusIdentical = [ordered] @{} foreach ($N in $Name) { foreach ($DC in $ForestInformation.ForestDomainControllers) { Write-Verbose -Message "Test-WinADDNSResolving - Processing $N on $($DC.Hostname)" try { $ResolvedDNS = Resolve-DnsName -Name $N -Server $DC.Hostname -Type $Type -ErrorAction Stop -DnsOnly:(-not $NotDNSOnly) -Verbose:$false $ErrorMessage = $null } catch { $ErrorMessage = $_.Exception.Message $ResolvedDNS = $null Write-Warning -Message "Test-WinADDNSResolving - Failed to resolve $N on $($DC.HostName). Error: $($_.Exception.Message)" } $Status = $false $ResolvedName = $null $ResolvedData = $null if ($ResolvedDNS) { if ($ResolvedDNS.Type -eq 'SOA') { $Status = $false } else { if ($Type -eq "PTR") { $ResolvedName = $ResolvedDNS.Name $ResolvedData = $ResolvedDNS.NameHost $Status = $true } elseif ($Type -eq "A") { $ResolvedName = $ResolvedDNS.Name $ResolvedData = $ResolvedDNS.IPAddress $Status = $true } elseif ($Type -eq 'AAAA') { $ResolvedName = $ResolvedDNS.Name $ResolvedData = $ResolvedDNS.IPAddress $Status = $true } elseif ($Type -eq "SRV") { $ResolvedName = $ResolvedDNS.Name $ResolvedData = $ResolvedDNS.NameTarget $Status = $true } elseif ($Type -eq 'CNAME') { $ResolvedName = $ResolvedDNS.Name $ResolvedData = $ResolvedDNS.NameHost $Status = $true } elseif ($Type -eq 'MX') { $OnlyMX = $ResolvedDNS | Where-Object { $_.QueryType -eq 'MX' } if ($OnlyMX) { $ResolvedName = $OnlyMX.Name $ResolvedData = "$($OnlyMX.Preference) $($OnlyMX.NameExchange)" $Status = $true } else { $ResolvedName = $null $ResolvedData = $null $Status = $false } } else { $ResolvedName = $ResolvedDNS.Name $ResolvedData = $ResolvedDNS.NameHost $Status = $true } } } if (-not $StatusIdentical[$N]) { $StatusIdentical[$N] = $ResolvedData $Identical = $true } else { if ($StatusIdentical[$N] -ne $ResolvedData) { $Identical = $false } else { $Identical = $true } } [PSCustomObject] @{ Name = $N Type = $Type DC = $DC.Hostname Resolving = $Status Identical = $Identical ErrorMessage = $ErrorMessage ResolvedName = $ResolvedName ResolvedData = $ResolvedData } } } } function Test-WinADObjectReplicationStatus { [CmdletBinding(DefaultParameterSetName = 'Standard')] param( [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'Analysis')] [string] $Identity, [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'Analysis')] [alias('ForestName')][string] $Forest, [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'Analysis')] [string[]] $ExcludeDomains, [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'Analysis')] [alias('Domain', 'Domains')][string[]] $IncludeDomains, # [Parameter(ParameterSetName = 'Standard')] # [Parameter(ParameterSetName = 'Analysis', Mandatory)] #[string] $SourceServer, [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'Analysis')] [switch] $GlobalCatalog, [Parameter(ParameterSetName = 'Analysis')] [string] $SnapshotPath, [Parameter(ParameterSetName = 'Standard')] [switch] $Sorted, [switch] $ClearSnapshot ) if ($SnapshotPath -and $ClearSnapshot) { if (Test-Path -LiteralPath $SnapshotPath) { Remove-Item -LiteralPath $SnapshotPath -Force -ErrorAction Stop } } $DistinguishedName = $Identity $DomainFromIdentity = ConvertFrom-DistinguishedName -DistinguishedName $Identity -ToDomainCN $ForestInformation = Get-WinADForestDetails -Extended -PreferWritable if ($GlobalCatalog) { [Array] $GCs = foreach ($DC in $ForestInformation.ForestDomainControllers) { if ($DC.IsGlobalCatalog) { $DC } } } else { [Array] $GCs = foreach ($DC in $ForestInformation.ForestDomainControllers) { if ($DC.Domain -eq $DomainFromIdentity) { $DC } } } $ResultsCached = [ordered] @{} $Results = foreach ($GC in $GCs) { # Query the specific object on each GC Try { if ($GlobalCatalog) { Write-Verbose -Message "Test-WinADObjectReplicationStatus - Querying $($GC.HostName) on port 3268 for $Identity" $ObjectInfo = Get-ADObject -Identity $Identity -Server "$($GC.HostName):3268" -Properties * -ErrorAction Stop } else { Write-Verbose -Message "Test-WinADObjectReplicationStatus - Querying $($GC.HostName) for $Identity" $ObjectInfo = Get-ADObject -Identity $Identity -Server $GC.HostName -Properties * -ErrorAction Stop } $ErrorValue = $null } catch { $ObjectInfo = $null Write-Warning "Test-WinADObjectReplicationStatus - Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))" $ErrorValue = $_.Exception.Message.Replace([System.Environment]::NewLine, '') } if ($ObjectInfo) { $PreparedObject = [PSCustomObject] @{ DomainController = $GC.HostName Domain = $GC.Domain UserAccountControl = $ObjectInfo.userAccountCOntrol Created = $ObjectInfo.Created uSNChanged = $ObjectInfo.uSNChanged uSNCreated = $ObjectInfo.uSNCreated whenCreated = $ObjectInfo.whenCreated WhenChanged = $ObjectInfo.WhenChanged Error = $ErrorValue } $ResultsCached[$GC.HostName] = $PreparedObject $PreparedObject } else { $PreparedObject = [PSCustomObject] @{ DomainController = $GC.HostName Domain = $GC.Domain UserAccountControl = $null Created = $null uSNChanged = $null uSNCreated = $null whenCreated = $null WhenChanged = $null Error = $ErrorValue } $ResultsCached[$GC.HostName] = $PreparedObject $PreparedObject } } if ($SnapshotPath) { $Date = Get-Date $DateText = $Date.ToString('yyyy-MM-dd HH:mm:ss') if (Test-Path -LiteralPath $SnapshotPath) { $Output = Import-Clixml -Path $SnapshotPath } else { $Output = [ordered] @{ $DistinguishedName = [ordered] @{} } } $Output[$DistinguishedName][$DateText] = [ordered] @{} foreach ($GC in $GCs) { $Output[$DistinguishedName][$DateText][$GC.Hostname] = [ordered] @{ Date = $Date USNChanged = $ResultsCached[$GC.Hostname].USNChanged WhenChanged = $ResultsCached[$GC.Hostname].WhenChanged } } foreach ($Key in [string[]] $Output.Keys | Where-Object { $_.Name -ne 'Summary' }) { if (-not $Output['Summary']) { $Output['Summary'] = [ordered] @{} } if ($Output[$DistinguishedName].Count -gt 1) { $Output['Summary'][$Key] = [ordered] @{} foreach ($TextDate in [string[]] $Output[$DistinguishedName].Keys | Select-Object -First 1) { $Output['Summary'][$Key][$TextDate] = [ordered] @{} foreach ($GC in $GCs) { $Output['Summary'][$Key][$GC.Hostname] = [ordered] @{ 'Name' = $GC.HostName 'Query' = if ($GlobalCatalog) { 'Global Catalog' } else { 'Domain Based' } 'WhenChanged' = $Output[$DistinguishedName][$TextDate][$GC.Hostname].WhenChanged 'USNChanged' = $Output[$DistinguishedName][$TextDate][$GC.Hostname].USNChanged } } } foreach ($TextDate in [string[]] $Output[$DistinguishedName].Keys | Select-Object -Skip 1) { foreach ($GC in $GCs) { if ($Output[$DistinguishedName][$TextDate][$GC.Hostname].USNChanged -ne $Output['Summary'][$Key][$GC.Hostname].UsnChanged) { $Status = 'Changed' } else { $Status = 'Not changed' } $Output['Summary'][$Key][$GC.Hostname][$TextDate] = $Status } } } } $Output | Export-Clixml -Path $SnapshotPath $Output } else { if ($Sorted) { $Results | Sort-Object -Property WhenChanged } else { $Results } } } function Test-WinADVulnerableSchemaClass { <# .SYNOPSIS Checks for CVE-2021-34470 and returns and object with output .DESCRIPTION Checks for CVE-2021-34470 and returns and object with output .EXAMPLE Test-WinADVulnerableSchemaClass .NOTES Based on https://microsoft.github.io/CSS-Exchange/Security/Test-CVE-2021-34470/ To repair either upgrade Microsoft Exchange Schema or run the fix from URL above #> [cmdletBinding()] param() $schemaMaster = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().SchemaRoleOwner $schemaDN = ([ADSI]"LDAP://$($schemaMaster)/RootDSE").schemaNamingContext $storageGroupSchemaEntryDN = "LDAP://$($schemaMaster)/CN=ms-Exch-Storage-Group,$schemaDN" if (-not ([System.DirectoryServices.DirectoryEntry]::Exists($storageGroupSchemaEntryDN))) { return [PSCustomObject] @{ "Vulnerable" = $false "Status" = "Exchange was not installed in this forest. Therefore, CVE-2021-34470 vulnerability is not present." "HasUnexpectedValue" = $false 'Superior' = $null } } $storageGroupSchemaEntry = [ADSI]($storageGroupSchemaEntryDN) if ($storageGroupSchemaEntry.Properties["possSuperiors"].Count -eq 0) { return [PSCustomObject] @{ "Vulnerable" = $false "Status" = "CVE-2021-34470 vulnerability is not present." "HasUnexpectedValue" = $false 'Superior' = $null } } foreach ($val in $storageGroupSchemaEntry.Properties["possSuperiors"]) { if ($val -eq "computer") { return [PSCustomObject] @{ "Vulnerable" = $true "Status" = "CVE-2021-34470 vulnerability is present." "HasUnexpectedValue" = $false 'Superior' = $null } } else { return [PSCustomObject] @{ "Vulnerable" = $true "Status" = "CVE-2021-34470 vulnerability may be present due to an unexpected superior: $val" "HasUnexpectedValue" = $true "Superior" = $val } } } } function Update-LastLogonTimestamp { <# .SYNOPSIS Uses Kerberos to impersonate a user and update the LastLogonTimestamp attribute .DESCRIPTION Uses Kerberos to impersonate a user and update the LastLogonTimestamp attribute It's a trick to last logon time updated without actually logging in .PARAMETER Identity The identity of the user to impersonate .EXAMPLE Update-LastLogonTimestamp -UserName 'PUID' .EXAMPLE Update-LastLogonTimestamp -UserName 'PUID@ad.evotec.xyz' .NOTES The lastLogontimeStamp attribute is not updated every time a user or computer logs on to the domain. The decision to update the value is based on the current date minus the value of the ( ms-DS-Logon-Time-Sync-Interval attribute minus a random percentage of 5). If the result is equal to or greater than lastLogontimeStamp the attribute is updated. If your Domain Admin is in Protected Users you may need to remove it from there to make it work #> [CmdletBinding(SupportsShouldProcess)] param ( [parameter(Position = 0, Mandatory)][alias('UserName')][string]$Identity ) begin { $impersonatedContext = $null $impersonationSuccessful = $false $ErrorMessage = $null } process { try { $windowsIdentity = [System.Security.Principal.WindowsIdentity]::new($Identity) } catch { Write-Warning "Update-LastLogonTimestamp - Failed to create WindowsIdentity for $Identity - $($_.Exception.Message)" $windowsIdentity = $null $ErrorMessage = $_.Exception.Message } if ($windowsIdentity) { try { if ($PSCmdlet.ShouldProcess("Impersonating user - $Identity")) { Write-Verbose "Update-LastLogonTimestamp - Impersonating user - $Identity" $impersonatedContext = $windowsIdentity.Impersonate() $impersonationSuccessful = $true } } catch { Write-Warning "Update-LastLogonTimestamp - Failed to impersonate user $Identity - $($_.Exception.Message)" $impersonationSuccessful = $false $ErrorMessage = $_.Exception.Message } finally { if ($impersonatedContext) { $impersonatedContext.Undo() } } } } end { [PSCustomObject] @{ Identity = $Identity WhatIf = $WhatIfPreference.ispresent UserName = $windowsIdentity.Name ImpersonationSuccessful = $impersonationSuccessful ErrorMessage = $ErrorMessage WindowsIdentity = $windowsIdentity } } } $ModuleFunctions = @{ ActiveDirectory = @{ 'Add-ADACL' = '' 'Copy-ADOUSecurity' = '' 'New-ADACLObject' = '' 'Enable-ADACLInheritance' = '' 'Disable-ADACLInheritance' = '' 'Export-ADACLObject' = '' 'Get-ADACL' = '' 'Get-ADACLOwner' = '' 'Get-WinADACLConfiguration' = '' 'Get-WinADACLForest' = '' 'Get-WinADBitlockerLapsSummary' = '' 'Get-WinADComputerACLLAPS' = '' 'Get-WinADComputers' = '' 'Get-WinADDelegatedAccounts' = '' 'Get-WinADDFSHealth' = '' 'Get-WinADDHCP' = '' 'Get-WinADDiagnostics' = '' 'Get-WinADDuplicateObject' = 'Get-WinADForestObjectsConflict' 'Get-WinADDuplicateSPN' = '' 'Get-WinADForestControllerInformation' = '' 'Get-WinADForestOptionalFeatures' = '' 'Get-WinADForestReplication' = '' 'Get-WinADForestRoles' = 'Get-WinADRoles', 'Get-WinADDomainRoles' 'Get-WinADForestSchemaProperties' = '' 'Get-WinADForestSites' = '' 'Get-WinADForestSubnet' = 'Get-WinADSubnet' 'Get-WinADLastBackup' = '' 'Get-WinADLDAPBindingsSummary' = '' 'Get-WinADLMSettings' = '' 'Get-WinADPrivilegedObjects' = 'Get-WinADPriviligedObjects' 'Get-WinADProtocol' = '' 'Get-WinADProxyAddresses' = '' 'Get-WinADServiceAccount' = '' 'Get-WinADSharePermission' = '' 'Get-WinADSiteConnections' = '' 'Get-WinADSiteLinks' = '' 'Get-WinADTomebstoneLifetime' = 'Get-WinADForestTomebstoneLifetime' 'Get-WinADTrustLegacy' = '' 'Get-WinADUserPrincipalName' = '' 'Get-WinADUsers' = '' 'Get-WinADUsersForeignSecurityPrincipalList' = 'Get-WinADUsersFP' 'Get-WinADWellKnownFolders' = '' 'Get-WinADPasswordPolicy' = '' 'Invoke-ADEssentials' = '' 'Remove-ADACL' = '' 'Remove-WinADDuplicateObject' = '' 'Remove-WinADSharePermission' = '' 'Rename-WinADUserPrincipalName' = '' 'Repair-WinADACLConfigurationOwner' = '' 'Repair-WinADEmailAddress' = '' 'Repair-WinADForestControllerInformation' = '' 'Set-ADACLOwner' = '' 'Set-DnsServerIP' = 'Set-WinDNSServerIP' 'Set-WinADDiagnostics' = '' 'Set-WinADReplication' = '' 'Set-WinADReplicationConnections' = '' 'Set-WinADShare' = '' 'Set-WinADTombstoneLifetime' = '' 'Show-WinADGroupCritical' = 'Show-WinADCriticalGroups' 'Show-WinADOrganization' = '' 'Show-WinADSites' = '' 'Show-WinADUserSecurity' = '' 'Sync-DomainController' = '' 'Test-ADDomainController' = '' 'Test-ADRolesAvailability' = '' 'Test-ADSiteLinks' = '' 'Test-DNSNameServers' = '' 'Test-FSMORolesAvailability' = '' 'Test-LDAP' = '' 'Get-WinDNSZones' = '' 'Get-WinDNSIPAddresses' = '' 'Find-WinADObjectDifference' = '' 'Show-WinADObjectDifference' = '' 'Test-WinADDNSResolving' = '' 'Get-WinADDomainControllerGenerationId' = '' } DHCPServer = @{ 'Get-WinADDHCP' = '' } DNSServer = @{ 'Get-WinADDnsInformation' = '' 'Get-WinADDNSIPAddresses' = 'Get-WinDnsIPAddresses' 'Get-WinADDNSRecords' = 'Get-WinDNSRecords' 'Get-WinADDnsServerForwarder' = '' 'Get-WinADDnsServerScavenging' = '' 'Get-WinADDnsServerZones' = '' 'Get-WinADDnsZones' = 'Get-WinDNSZones' 'Remove-WinADDnsRecord' = '' } } [Array] $FunctionsAll = 'Add-ADACL', 'Copy-ADOUSecurity', 'Disable-ADACLInheritance', 'Enable-ADACLInheritance', 'Export-ADACLObject', 'Find-WinADObjectDifference', 'Get-ADACL', 'Get-ADACLOwner', 'Get-ADWinDnsServerZones', 'Get-DNSServerIP', 'Get-WinADACLConfiguration', 'Get-WinADACLForest', 'Get-WinADBitlockerLapsSummary', 'Get-WinADComputerACLLAPS', 'Get-WinADComputers', 'Get-WinADDelegatedAccounts', 'Get-WinADDFSHealth', 'Get-WinADDFSTopology', 'Get-WinADDHCP', 'Get-WinADDiagnostics', 'Get-WinADDnsInformation', 'Get-WinADDnsIPAddresses', 'Get-WinADDnsRecords', 'Get-WinADDnsServerForwarder', 'Get-WinADDnsServerScavenging', 'Get-WinADDNSZones', 'Get-WinADDomain', 'Get-WinADDomainControllerGenerationId', 'Get-WinADDomainControllerOption', 'Get-WinADDuplicateObject', 'Get-WinADDuplicateSPN', 'Get-WinADForest', 'Get-WinADForestControllerInformation', 'Get-WinADForestOptionalFeatures', 'Get-WinADForestReplication', 'Get-WinADForestReplicationSummary', 'Get-WinADForestRoles', 'Get-WinADForestSchemaProperties', 'Get-WinADForestSites', 'Get-WinADForestSubnet', 'Get-WinADGroupMember', 'Get-WinADGroupMemberOf', 'Get-WinADGroups', 'Get-WinADKerberosAccount', 'Get-WinADLastBackup', 'Get-WinADLDAPBindingsSummary', 'Get-WinADLMSettings', 'Get-WinADObject', 'Get-WinADPasswordPolicy', 'Get-WinADPrivilegedObjects', 'Get-WinADProtocol', 'Get-WinADProxyAddresses', 'Get-WinADServiceAccount', 'Get-WinADSharePermission', 'Get-WinADSiteConnections', 'Get-WinADSiteLinks', 'Get-WinADSiteOptions', 'Get-WinADTomebstoneLifetime', 'Get-WinADTrust', 'Get-WinADTrustLegacy', 'Get-WinADUserPrincipalName', 'Get-WinADUsers', 'Get-WinADUsersForeignSecurityPrincipalList', 'Get-WinADWellKnownFolders', 'Invoke-ADEssentials', 'New-ADACLObject', 'New-ADSite', 'Remove-ADACL', 'Remove-WinADDFSTopology', 'Remove-WinADDuplicateObject', 'Remove-WinADSharePermission', 'Rename-WinADUserPrincipalName', 'Repair-WinADACLConfigurationOwner', 'Repair-WinADEmailAddress', 'Repair-WinADForestControllerInformation', 'Request-ChangePasswordAtLogon', 'Request-DisableOnAccountExpiration', 'Restore-ADACLDefault', 'Set-ADACL', 'Set-ADACLInheritance', 'Set-ADACLOwner', 'Set-DnsServerIP', 'Set-WinADDiagnostics', 'Set-WinADDomainControllerOption', 'Set-WinADForestACLOwner', 'Set-WinADReplication', 'Set-WinADReplicationConnections', 'Set-WinADShare', 'Set-WinADTombstoneLifetime', 'Show-WinADDNSRecords', 'Show-WinADGroupCritical', 'Show-WinADGroupMember', 'Show-WinADGroupMemberOf', 'Show-WinADKerberosAccount', 'Show-WinADObjectDifference', 'Show-WinADOrganization', 'Show-WinADSites', 'Show-WinADTrust', 'Show-WinADUserSecurity', 'Sync-WinADDomainController', 'Test-ADDomainController', 'Test-ADRolesAvailability', 'Test-ADSiteLinks', 'Test-DNSNameServers', 'Test-FSMORolesAvailability', 'Test-LDAP', 'Test-WinADDNSResolving', 'Test-WinADObjectReplicationStatus', 'Test-WinADVulnerableSchemaClass', 'Update-LastLogonTimestamp' [Array] $AliasesAll = 'Get-WinADDomainRoles', 'Get-WinADForestObjectsConflict', 'Get-WinADForestTomebstoneLifetime', 'Get-WinADPriviligedObjects', 'Get-WinADRoles', 'Get-WinADSubnet', 'Get-WinADTrusts', 'Get-WinADUsersFP', 'Get-WinDnsIPAddresses', 'Get-WinDNSRecords', 'Get-WinDNSServerIP', 'Get-WinDnsServerZones', 'Get-WinDNSZones', 'Set-WinDNSServerIP', 'Show-ADGroupMember', 'Show-ADGroupMemberOf', 'Show-ADTrust', 'Show-ADTrusts', 'Show-WinADCriticalGroups', 'Show-WinADTrusts', 'Sync-DomainController' $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 functions and aliases as required Export-ModuleMember -Function @($FunctionsToLoad) -Alias @($AliasesToLoad) # SIG # Begin signature block # MIItsQYJKoZIhvcNAQcCoIItojCCLZ4CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCGhgkCdQRaZVCv # NQqgeQvAQCg5m8pcduzeLjflvN6NFqCCJrQwggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw # aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK # EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm # dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu # d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD # eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1 # XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld # QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS # YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm # M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT # QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx # fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD # VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq # hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4 # XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ # aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg # X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk # apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL # FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy # 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u # KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54 # zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8 # 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8 # aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w # ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS # g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9 # /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn # HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0 # VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f # sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj # gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0 # QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv # mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T # /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk # 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r # mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n # P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG # CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV # HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB # AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp # wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl # zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ # cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe # Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j # Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh # IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6 # OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw # N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR # 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2 # VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ # CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV # MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t # MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw # MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k # jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9 # NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9 # URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY # E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS # 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa # wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w # c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR # Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2 # 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK # ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC # AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2 # O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P # MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB # AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr # BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH # BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6 # mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/ # SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY # gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9 # kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ # 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew # Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm # Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA # SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr # y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR # ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu # v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/ # X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5 # NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAx # MzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu # MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOX # ejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbj # aedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7 # ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PB # uOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu # 6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769Sg # LDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUG # FOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZc # ClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmh # cbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U2 # 0clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qD # y0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW # BgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg # hkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0O # BBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6 # Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy # NTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUF # BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6 # Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZT # SEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6g # qbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s # 1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0q # BXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp4 # 4pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6w # QSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4Z # iQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wn # LEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5Adza # ROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy # 0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDA # dwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2Xl # G3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIHXzCCBUegAwIBAgIQB8JSdCgU # otar/iTqF+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE # ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg # Q29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAw # MDAwMFoXDTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1p # a2/FgsOzdzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYD # VQQDDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmV # OrRBVRQA8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVE # h0C/Daehvxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNd # GVXRYOLn47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0 # 235CN4RrW+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuA # o3+jVB8wiUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw # 8/FNzGNPlAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP # 0ib98XLfQpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxi # W4oHYO28eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFK # RqwvSSr4fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKA # BGoIqSW05nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQID # AQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYD # VR0OBBYEFHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNV # HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js # My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw # OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy # MUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0 # cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggr # BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBo # dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl # U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqG # SIb3DQEBCwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50 # ZHzoWs6EBlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa # 1W47YSrc5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2 # CbE3JroJf2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0 # djvQSx510MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N # 9E8hUVevxALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgi # zpwBasrxh6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38 # wwtaJ3KYD0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Y # n8kQMB6/Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Z # n3exUAKqG+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe # 6nB6bSYHv8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGC # BlMwggZPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ # bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS # U0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgB # ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ # AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G # CSqGSIb3DQEJBDEiBCDgNHE0AVsomEiZ/F5EUZlhRrLTd5TtmP+LB77RUZEBCzAN # BgkqhkiG9w0BAQEFAASCAgAAgMJIulIqb+kGUsB8FTeaHt49OeCJXfEgL1UeCGyC # Vcj8Da5HvnOPusFhcHCNbkpECabrdDG7P/tLyoLQKt7XXXO2eT0RxDTtNmWxc5M3 # BbAF/dJ6VZE4M4/oEH0wmt6K2ai5Whi86RdENMYEiNoqlEJjuvpcsg2jI0b/9j1y # owYIQQtjv+uY5rjA17AmgUhQG67WfB9Tt2vA3wJESnUsht5m+Y/din3Ae1vJmokD # TBawYkOBkkZrlw/HkpZqmML39wLPsIL32L4HqWF3RLIPgpB7mSJA85gD1js/nfYX # fpzk/KEhCRQhiOWpu6gjVJcuwQVgVtJ/xfp9krSOsk81i0Itat1NdqUvmhojTF3L # HFIvbdTwNksoFdvbeQqLdEUGTIgy3wv/fdE+YWJQu5SrfgEovOs+s0OojYNRIKyL # quUC2i5f8QvHiXu6uddOZR0l/JZSmHLqgzcD1VIfXJGlbuCjCf+apU22cnlNW/5k # SMWVhTaymtGlzmjLCT+1fLdFeXZcZUqtpocEeSzW3ejFKbMnceKbi+m+hUoKNcTM # Ssc9TpoM7O/7EkZqPJ2tAWalCxWEnp50x63PbkydmzffHvXJIEsXFEfPJZJ3TJGJ # UZTactaGz6Bd6UM3tSCRpF8tYKLsf0Fm4EZK/DNgtw8h2nMdL3TnjJlgU7BzGgeL # WKGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5 # pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN # AQcBMBwGCSqGSIb3DQEJBTEPFw0yNDA0MTYxNTU5MzhaMC8GCSqGSIb3DQEJBDEi # BCAPcb/0NWYunaKzJBA2JvNV3dGlACeiLFO4bfkC+XtOzTANBgkqhkiG9w0BAQEF # AASCAgBGeMFywDRP2ICDD+U9r+HlqP+oJYADw46qDU6CQJaj6tgiL/PA+OIUMTfk # w7miX66FkMihfji0V7g5ZjpFteQ42CN9hI2j9H1CFLFJ7O+UBdSekQzAdHGy4AQc # NdPZGUGvfsDCp7XJI3CvjWs+3F5zamCDYVADWns83KgECuhtMuw/8kyZNmLIWj4d # djIdMIiIDGAHA4bkYsYzlx3Qk4HuY42yqe0m1mx7SjxSOQJHecwA1PNP1nhhCoB1 # /zvjJ1Q0bWEdCuKMoJg7F2pXcUNiFKtg6wovXJIqLVqASw25JWgGmefIEncFKlGm # Ii9zXMMCAjnjahfI7kDipLQn3Jd1v29YueYTclYlKbvIhhQfXTuVK/9F0nXBpUrv # mUxZ7/MlsXKtfawHVg6dHuwzQKmgkqqff2IBhqXx6t1vmCxRhmuctkKB41bjBuRe # 3Vra9a3Y2Xfe75hnwNKyggqCE2CgGchJGUNZQPej5SWWpexlh6C1mNQFRgyza2KJ # qBsBsOCaisvNUMEQyfBT88stUqOTU8ig4of7/PeMFP59RkTcalCSE/BAAsZ6oFdq # l4T89rYqzBgpx07gekCosLBOuiFc7/s5Uyw/0WowjDD8MPZVKx4am4e7hk3yDBS5 # QB7N9KEl8avEvj5+uFErfd+ag3fbAe9zuYNwufu/3rrsQSIrJA== # SIG # End signature block |