diff --git a/Private/Get-GPOZaurrLinkLoop.ps1 b/Private/Get-GPOZaurrLinkLoop.ps1 new file mode 100644 index 0000000..b7b8eae --- /dev/null +++ b/Private/Get-GPOZaurrLinkLoop.ps1 @@ -0,0 +1,106 @@ +function Get-GPOZaurrLinkLoop { + [cmdletBinding()] + param( + [Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject, + [System.Collections.IDictionary] $CacheReturnedGPOs, + [System.Collections.IDictionary] $ForestInformation, + [validateset('All', 'Root', 'DomainControllers', 'Site', 'Other')][string[]] $Linked, + [string] $SearchBase, + [Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope, + [string] $Filter + ) + if (-not $ADObject) { + if (-not $Filter) { + # if not linked, we force it to All + if (-not $Linked) { + $Linked = 'All' + } + foreach ($Domain in $ForestInformation.Domains) { + $Splat = @{ + #Filter = $Filter + Properties = 'distinguishedName', 'gplink', 'CanonicalName' + # Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')" + Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] + } + if ($Linked -contains 'Root' -or $Linked -contains 'All') { + $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] + $Splat['Filter'] = "objectClass -eq 'domainDNS'" + $Splat['SearchBase'] = $SearchBase + try { + $ADObjectGPO = Get-ADObject @Splat + } catch { + Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" + } + Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable + } + if ($Linked -contains 'Site' -or $Linked -contains 'All') { + # Sites are defined only in primary domain + if ($ForestInformation['DomainsExtended'][$Domain]['DNSRoot'] -eq $ForestInformation['DomainsExtended'][$Domain]['Forest']) { + $SearchBase = -join ("CN=Configuration,", $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) + $Splat['Filter'] = "(objectClass -eq 'site')" + $Splat['SearchBase'] = $SearchBase + try { + $ADObjectGPO = Get-ADObject @Splat + } catch { + Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" + } + Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable + } + } + if ($Linked -contains 'DomainControllers' -or $Linked -contains 'All') { + $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer'] + $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')" + $Splat['SearchBase'] = $SearchBase + try { + $ADObjectGPO = Get-ADObject @Splat + } catch { + Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" + } + Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable + } + if ($Linked -contains 'Other' -or $Linked -contains 'All') { + $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] + $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')" + $Splat['SearchBase'] = $SearchBase + try { + $ADObjectGPO = Get-ADObject @Splat + } catch { + Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" + } + Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -SkipDomainRoot -SkipDomainControllers -AsHashTable:$AsHashTable + } + } + } elseif ($Filter) { + foreach ($Domain in $ForestInformation.Domains) { + $Splat = @{ + Filter = $Filter + Properties = 'distinguishedName', 'gplink', 'CanonicalName' + Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] + + } + if ($PSBoundParameters.ContainsKey('SearchBase')) { + $DomainDistinguishedName = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] + $SearchBaseDC = ConvertFrom-DistinguishedName -DistinguishedName $SearchBase -ToDC + if ($SearchBaseDC -ne $DomainDistinguishedName) { + # we check if SearchBase is part of domain distinugishname. If it isn't we skip + continue + } + $Splat['SearchBase'] = $SearchBase + + } + if ($PSBoundParameters.ContainsKey('SearchScope')) { + $Splat['SearchScope'] = $SearchScope + } + + try { + $ADObjectGPO = Get-ADObject @Splat + } catch { + Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" + } + Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable + } + } + } else { + Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObject -Domain '' -ForestInformation $ForestInformation -AsHashTable:$AsHashTable + } +} \ No newline at end of file diff --git a/Private/Get-PrivGPOZaurrLInk.ps1 b/Private/Get-PrivGPOZaurrLInk.ps1 index 07cd47b..ae16adf 100644 --- a/Private/Get-PrivGPOZaurrLInk.ps1 +++ b/Private/Get-PrivGPOZaurrLInk.ps1 @@ -6,91 +6,68 @@ [System.Collections.IDictionary] $GPOCache ) if ($Object.GpLink -and $Object.GpLink.Trim() -ne '') { - $Object.GpLink -split '\]\[' | ForEach-Object -Process { - $Link = $_ -replace '\[LDAP://' -replace '\]' -replace '\[' - if ($Link.Length -gt 10) { - $SplitGPLink = $Link -split ';' - $DN = $SplitGPLink[0] - $Option = $SplitGPLink[1] - if ($Option -eq '0') { - $Enforced = $false - $Enabled = $true - } elseif ($Option -eq '1') { - $Enabled = $false - $Enforced = $false - } elseif ($Option -eq '2') { - $Enabled = $true - $Enforced = $true - } elseif ($Option -eq '3') { - $Enabled = $false - $Enforced = $true - } else { + $ObjectsToProcess = $Object.GpLink -split '\]\[' + } elseif ($Object.LinkedGroupPolicyObjects -and $Object.LinkedGroupPolicyObjects.Trim() -ne '') { + $ObjectsToProcess = $Object.LinkedGroupPolicyObjects -split '\]\[' + } else { + $ObjectsToProcess = $null + } + $ObjectsToProcess | ForEach-Object -Process { + $Link = $_ -replace 'LDAP://' -replace '\]' -replace '\[' + if ($Link.Length -gt 10) { + $SplitGPLink = $Link -split ';' + $DN = $SplitGPLink[0] + $Option = $SplitGPLink[1] + if ($Option -eq '0') { + $Enforced = $false + $Enabled = $true + } elseif ($Option -eq '1') { + $Enabled = $false + $Enforced = $false + } elseif ($Option -eq '2') { + $Enabled = $true + $Enforced = $true + } elseif ($Option -eq '3') { + $Enabled = $false + $Enforced = $true + } else { + if ($Object.GpLink) { Write-Warning "Get-PrivGPOZaurrLink - This should't happen. Please investigate - Option: $Option" - $Enabled = $null - $Enforced = $null - } - $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDomainCN - $Output = [ordered] @{ - DistinguishedName = $Object.DistinguishedName - CanonicalName = if ($Object.CanonicalName) { $Object.CanonicalName.TrimEnd('/') } else { $Object.CanonicalName } - Guid = [Regex]::Match( $DN, '(?={)(.*)(?<=})').Value -replace '{' -replace '}' - Enforced = $Enforced - Enabled = $Enabled - ObjectClass = $Object.ObjectClass - } - $Search = -join ($DomainCN, $Output['Guid']) - if ($GPOCache -and -not $Limited) { - if ($GPOCache[$Search]) { - $Output['DisplayName'] = $GPOCache[$Search].DisplayName - $Output['DomainName'] = $GPOCache[$Search].DomainName - $Output['Owner'] = $GPOCache[$Search].Owner - $Output['GpoStatus'] = $GPOCache[$Search].GpoStatus - $Output['Description'] = $GPOCache[$Search].Description - $Output['CreationTime'] = $GPOCache[$Search].CreationTime - $Output['ModificationTime'] = $GPOCache[$Search].ModificationTime - $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDC - $Output['GPODistinguishedName'] = $DN - [PSCustomObject] $Output - } else { - Write-Warning "Get-PrivGPOZaurrLink - Couldn't find link $Search in a GPO Cache. Lack of permissions for given GPO? Are you running as admin? Skipping." - } } else { + Write-Warning "Get-PrivGPOZaurrLink - Property GPLink is required to be able to tell if Enabled/Enforced is added. Skipping those settings." + } + $Enabled = $null + $Enforced = $null + } + $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDomainCN + $Output = [ordered] @{ + DistinguishedName = $Object.DistinguishedName + CanonicalName = if ($Object.CanonicalName) { $Object.CanonicalName.TrimEnd('/') } else { $Object.CanonicalName } + Guid = [Regex]::Match($DN, '(?={)(.*)(?<=})').Value -replace '{' -replace '}' + Enforced = $Enforced + Enabled = $Enabled + ObjectClass = $Object.ObjectClass + } + $Search = -join ($DomainCN, $Output['Guid']) + if ($GPOCache -and -not $Limited) { + if ($GPOCache[$Search]) { + $Output['DisplayName'] = $GPOCache[$Search].DisplayName + $Output['DomainName'] = $GPOCache[$Search].DomainName + $Output['Owner'] = $GPOCache[$Search].Owner + $Output['GpoStatus'] = $GPOCache[$Search].GpoStatus + $Output['Description'] = $GPOCache[$Search].Description + $Output['CreationTime'] = $GPOCache[$Search].CreationTime + $Output['ModificationTime'] = $GPOCache[$Search].ModificationTime $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDC $Output['GPODistinguishedName'] = $DN [PSCustomObject] $Output - } - } - } - } elseif ($Object.LinkedGroupPolicyObjects -and $Object.LinkedGroupPolicyObjects.Trim() -ne '') { - $Object.LinkedGroupPolicyObjects -split '\[LDAP://' -split ';' | ForEach-Object -Process { - if ($_.Length -gt 10) { - $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $_ -ToDomainCN - $Output = [ordered] @{ - DistinguishedName = $Object.DistinguishedName - CanonicalName = if ($Object.CanonicalName) { $Object.CanonicalName.TrimEnd('/') } else { $Object.CanonicalName } - Guid = [Regex]::Match( $_, '(?={)(.*)(?<=})').Value -replace '{' -replace '}' - } - $Search = -join ($DomainCN, $Output['Guid']) - if ($GPOCache -and -not $Limited) { - if ($GPOCache[$Search]) { - $Output['Name'] = $GPOCache[$Search].DisplayName - $Output['DomainName'] = $GPOCache[$Search].DomainName - $Output['Owner'] = $GPOCache[$Search].Owner - $Output['GpoStatus'] = $GPOCache[$Search].GpoStatus - $Output['Description'] = $GPOCache[$Search].Description - $Output['CreationTime'] = $GPOCache[$Search].CreationTime - $Output['ModificationTime'] = $GPOCache[$Search].ModificationTime - $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $_ -ToDC - $Output['GPODistinguishedName'] = $_ - [PSCustomObject] $Output - } else { - Write-Warning "Get-PrivGPOZaurrLink - Couldn't find link $Search in a GPO Cache. Lack of permissions for given GPO? Are you running as admin? Skipping." - } } else { - $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $_ -ToDC - $Output['GPODistinguishedName'] = $_ - [PSCustomObject] $Output + Write-Warning "Get-PrivGPOZaurrLink - Couldn't find link $Search in a GPO Cache. Lack of permissions for given GPO? Are you running as admin? Skipping." } + } else { + $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDC + $Output['GPODistinguishedName'] = $DN + [PSCustomObject] $Output } } } diff --git a/Public/Get-GPOZaurrLink.ps1 b/Public/Get-GPOZaurrLink.ps1 index 008e8f1..bda9319 100644 --- a/Public/Get-GPOZaurrLink.ps1 +++ b/Public/Get-GPOZaurrLink.ps1 @@ -47,7 +47,12 @@ [parameter(ParameterSetName = 'Filter')] [parameter(ParameterSetName = 'ADObject')] [parameter(ParameterSetName = 'Linked')] - [switch] $AsHashTable + [switch] $AsHashTable, + + [parameter(ParameterSetName = 'Filter')] + [parameter(ParameterSetName = 'ADObject')] + [parameter(ParameterSetName = 'Linked')] + [switch] $Summary ) Begin { $CacheReturnedGPOs = [ordered] @{} @@ -65,99 +70,58 @@ } } Process { - if (-not $ADObject) { - if (-not $Filter) { - # if not linked, we force it to All - if (-not $Linked) { - $Linked = 'All' - } - foreach ($Domain in $ForestInformation.Domains) { - $Splat = @{ - #Filter = $Filter - Properties = 'distinguishedName', 'gplink', 'CanonicalName' - # Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')" - Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] - } - if ($Linked -contains 'Root' -or $Linked -contains 'All') { - $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] - $Splat['Filter'] = "objectClass -eq 'domainDNS'" - $Splat['SearchBase'] = $SearchBase - try { - $ADObjectGPO = Get-ADObject @Splat - } catch { - Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" - } - Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable - } - if ($Linked -contains 'Site' -or $Linked -contains 'All') { - # Sites are defined only in primary domain - if ($ForestInformation['DomainsExtended'][$Domain]['DNSRoot'] -eq $ForestInformation['DomainsExtended'][$Domain]['Forest']) { - $SearchBase = -join ("CN=Configuration,", $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) - $Splat['Filter'] = "(objectClass -eq 'site')" - $Splat['SearchBase'] = $SearchBase - try { - $ADObjectGPO = Get-ADObject @Splat - } catch { - Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" - } - Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable - } - } - if ($Linked -contains 'DomainControllers' -or $Linked -contains 'All') { - $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer'] - $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')" - $Splat['SearchBase'] = $SearchBase - try { - $ADObjectGPO = Get-ADObject @Splat - } catch { - Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" - } - Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable - } - if ($Linked -contains 'Other' -or $Linked -contains 'All') { - $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] - $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')" - $Splat['SearchBase'] = $SearchBase - try { - $ADObjectGPO = Get-ADObject @Splat - } catch { - Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" - } - Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -SkipDomainRoot -SkipDomainControllers -AsHashTable:$AsHashTable - } - } - } elseif ($Filter) { - foreach ($Domain in $ForestInformation.Domains) { - $Splat = @{ - Filter = $Filter - Properties = 'distinguishedName', 'gplink', 'CanonicalName' - Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0] + $getGPOLoopSplat = @{ + Linked = $Linked + ForestInformation = $ForestInformation + CacheReturnedGPOs = $CacheReturnedGPOs + SearchScope = $SearchScope + SearchBase = $SearchBase + ADObject = $ADObject + Filter = $Filter + } + Remove-EmptyValue -Hashtable $getGPOLoopSplat -Recursive - } - if ($PSBoundParameters.ContainsKey('SearchBase')) { - $DomainDistinguishedName = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'] - $SearchBaseDC = ConvertFrom-DistinguishedName -DistinguishedName $SearchBase -ToDC - if ($SearchBaseDC -ne $DomainDistinguishedName) { - # we check if SearchBase is part of domain distinugishname. If it isn't we skip - continue - } - $Splat['SearchBase'] = $SearchBase - - } - if ($PSBoundParameters.ContainsKey('SearchScope')) { - $Splat['SearchScope'] = $SearchScope - } - - try { - $ADObjectGPO = Get-ADObject @Splat - } catch { - Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)" - } - Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable + if ($AsHashTable -or $Summary) { + $HashTable = [ordered] @{} + $SummaryHashtable = [ordered] @{} + $Links = Get-GPOZaurrLinkLoop @getGPOLoopSplat + foreach ($Link in $Links) { + $Key = -join ($Link.DomainName, $Link.GUID) + if (-not $HashTable[$Key]) { + $HashTable[$Key] = [System.Collections.Generic.List[PSCustomObject]]::new() } + $HashTable[$Key].Add($Link) + } + foreach ($Key in $HashTable.Keys) { + [Array] $Link = $HashTable[$Key] + $EnabledLinks = $Link.Enabled.Where( { $_ -eq $true }, 'split') + if ($EnabledLinks[0].Count -gt 0) { + $IsLinked = $true + } else { + $IsLinked = $false + } + $SummaryLink = [PSCustomObject] @{ + DisplayName = $Link[0].DisplayName + DomainName = $Link[0].DomainName + GUID = $Link[0].GUID + Linked = $IsLinked + LinksCount = $Link.Count + LinksEnabledCount = $EnabledLinks[0].Count + LinksDisabledCount = $EnabledLinks[1].Count + Links = $Link.DistinguishedName + LinksObjects = $Link + } + $SummaryHashtable[$Key] = $SummaryLink + } + if ($AsHashTable -and $Summary) { + $SummaryHashtable + } elseif ($AsHashTable) { + $HashTable + } elseif ($Summary) { + $SummaryHashtable.Values } } else { - Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObject -Domain '' -ForestInformation $ForestInformation -AsHashTable:$AsHashTable + Get-GPOZaurrLinkLoop @getGPOLoopSplat } } End { diff --git a/Public/Get-GPOZaurrLinkSummary.ps1 b/Public/Get-GPOZaurrLinkSummary.ps1 index 27932a4..ad8b80e 100644 --- a/Public/Get-GPOZaurrLinkSummary.ps1 +++ b/Public/Get-GPOZaurrLinkSummary.ps1 @@ -13,7 +13,7 @@ $CacheSummaryLinks = [ordered] @{} # cache # Get all links - $Links = Get-GPOZaurrLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation + $Links = Get-GPOZaurrLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Linked Root, DomainControllers, Other foreach ($Link in $Links) { if (-not $CacheSummaryLinks["$($Link.DomainName)$($Link.Guid)"]) { $CacheSummaryLinks["$($Link.DomainName)$($Link.Guid)"] = [System.Collections.Generic.List[System.Object]]::new()