Compare commits

...

43 Commits

Author SHA1 Message Date
Przemyslaw Klys a24b1ff958 Added new report 2021-01-04 09:25:02 +01:00
Przemyslaw Klys de076a1cb4 Added new report 2021-01-04 09:24:55 +01:00
Przemyslaw Klys 663eecc01b Small typo fix for GPODuplicate 2021-01-04 09:24:48 +01:00
Przemyslaw Klys d1b7d71ad9 Update readme 2021-01-04 09:24:35 +01:00
Przemyslaw Klys d888f135e0 Update readme 2021-01-03 20:58:13 +01:00
Przemyslaw Klys 81ecf580c4 Added Repair-GPOZaurrBrokenLink 2021-01-03 20:58:06 +01:00
Przemyslaw Klys 34c9d59150 Added Get-GPOZaurrBrokenLink 2021-01-03 20:57:27 +01:00
Przemyslaw Klys 80580b7dce Update PSD1 2021-01-03 20:55:53 +01:00
Przemyslaw Klys 304f70a4d9 PSD1 update 2021-01-02 16:40:03 +01:00
Przemyslaw Klys ab6c61ac52 Update ReADME 2021-01-02 16:39:58 +01:00
Przemyslaw Klys dbe79a7833 Update PSD1 2020-12-28 12:15:35 +01:00
Przemyslaw Klys 0cc05c4aeb Update readme 2020-12-28 12:06:46 +01:00
Przemyslaw Klys c10a4c088e Improved GPOZaurr 2020-12-28 12:06:40 +01:00
Przemyslaw Klys 015afd69a4 Update 2020-12-28 11:53:29 +01:00
Przemyslaw Klys 2fa9b202bc Example updates 2020-12-28 11:42:30 +01:00
Przemyslaw Klys 25bbf8d52e Update 2020-12-28 11:42:04 +01:00
Przemyslaw Klys 8a009bf288 Update 2020-12-28 11:16:59 +01:00
Przemyslaw Klys ed76fc9513 Update example 2020-12-28 10:31:56 +01:00
Przemyslaw Klys 646c22bdab Update example 2020-12-28 10:31:35 +01:00
Przemyslaw Klys d4f8cdc8e8 Update examples 2020-12-28 10:31:25 +01:00
Przemyslaw Klys 166d4fb767 Impoved Get-GPOzaurr Links 2020-12-28 10:30:56 +01:00
Przemyslaw Klys 0ca98a4165 Improvement to links 2020-12-28 10:04:56 +01:00
Przemyslaw Klys a5bcbbf285 Update 2020-12-28 10:04:27 +01:00
Przemyslaw Klys 513179fd03 Invoke-GPOZaurrPermission improvement 2020-12-28 10:04:15 +01:00
Przemyslaw Klys 72cd1da9ae update readme 2020-12-28 10:03:29 +01:00
Przemyslaw Klys 3fe52c99b5 Private use only 2020-12-28 09:55:14 +01:00
Przemyslaw Klys fc6fe14579 Update readme 2020-12-27 23:25:34 +01:00
Przemyslaw Klys bd5a73439d Update 2020-12-27 23:25:15 +01:00
Przemyslaw Klys 469332e543 PSD1 update 2020-12-27 22:38:57 +01:00
Przemyslaw Klys 6cbe3a375a Improved Get-GPOZaurrLink 2020-12-27 22:38:48 +01:00
Przemyslaw Klys c987ca9863 Update 2020-12-27 22:38:31 +01:00
Przemyslaw Klys f0d24e1902 Update 2020-12-27 22:38:05 +01:00
Przemyslaw Klys 61b9c916e7 Update 2020-12-24 09:32:15 +01:00
Przemyslaw Klys deebcb825d Update Get-GPOZaurrLink 2020-12-24 09:27:57 +01:00
Przemyslaw Klys 7adfd1ef65 PSD1 2020-12-23 11:06:39 +01:00
Przemyslaw Klys 828c221ac9 Renamed GPOBroken 2020-12-23 10:46:07 +01:00
Przemyslaw Klys 8e80fd423d Update readme 2020-12-23 10:45:55 +01:00
Przemyslaw Klys f52fd60fef Update 2020-12-23 10:33:30 +01:00
Przemyslaw Klys 9594afb246 Update example 2020-12-23 10:00:53 +01:00
Przemyslaw Klys 7c52284b48 Improved Remove-GPOZaurrBroken 2020-12-23 09:47:14 +01:00
Przemyslaw Klys 2fd64952af Update readme 2020-12-23 09:47:04 +01:00
Przemyslaw Klys 15538e8559 Improve Get-GPOZaurrBroken 2020-12-23 09:32:07 +01:00
Przemyslaw Klys a36a470538 Update readme 2020-12-23 09:31:57 +01:00
32 changed files with 1354 additions and 393 deletions
@@ -0,0 +1,5 @@
Import-Module "$PSScriptRoot\..\GPoZaurr.psd1" -Force
$SummaryHashtable = Get-GPOZaurrLink -AsHashTable -Summary
$SummaryHashtable | Format-Table -AutoSize
$SummaryHashtable[5]
+1 -1
View File
@@ -2,6 +2,6 @@
Get-GPOZaurrLink -SearchBase 'OU=Domain Controllers,DC=ad,DC=evotec,DC=xyz' | Format-Table -AutoSize
Get-GPOZaurrLink -SearchBase 'OU=Computers,OU=Production,DC=ad,DC=evotec,DC=xyz' | Format-Table -AutoSize
Get-GPOZaurrLink -SearchBase 'OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz' | Format-Table -AutoSize
Get-GPOZaurrLink -SearchBase 'DC=ad,DC=evotec,DC=xyz' -SearchScope Base | Format-Table -AutoSize
+3 -2
View File
@@ -1,6 +1,7 @@
Import-Module "$PSScriptRoot\..\GPoZaurr.psd1" -Force
Get-ADOrganizationalUnit -Filter * -Properties distinguishedName, LinkedGroupPolicyObjects | Get-GPOZaurrLink | Format-Table
Get-ADOrganizationalUnit -Filter * -Properties canonicalname, distinguishedName, LinkedGroupPolicyObjects | Get-GPOZaurrLink | Format-Table
# gpLink is required property. While LinkedGroupPolicyObjects does work it doesn't contain information about enabled/enforced GPO
Get-ADOrganizationalUnit -Filter * -Properties distinguishedName, LinkedGroupPolicyObjects, gpLink | Get-GPOZaurrLink | Format-Table
Get-ADOrganizationalUnit -Filter * -Properties canonicalname, distinguishedName, LinkedGroupPolicyObjects, gpLink | Get-GPOZaurrLink | Format-Table
Get-ADObject -Filter * -Properties distinguishedName, gplink -Server 'ad.evotec.pl' | Get-GPOZaurrLink | Format-Table -AutoSize
Get-GPOZaurrLink | Format-Table -AutoSize
@@ -0,0 +1,3 @@
Import-Module "$PSScriptRoot\..\GPoZaurr.psd1" -Force
Get-GPOZaurrLink -Site 'Katowice-1','Katowice-2' | Format-Table -AutoSize *
@@ -0,0 +1,3 @@
Import-Module "$PSScriptRoot\..\GPoZaurr.psd1" -Force
Get-GPOZaurrLink -Linked Site | Format-Table -AutoSize *
@@ -0,0 +1,8 @@
Import-Module "$PSScriptRoot\..\GPoZaurr.psd1" -Force
$Summary = Get-GPOZaurrLink -Summary
$Summary | Format-Table -AutoSize *
$OneGPO = $Summary | Where-Object { $_.DisplayName -eq 'ALL | Enable RDP' }
$OneGPO.Links
$OneGPO.LinksObjects | Format-Table *
@@ -1,10 +1,10 @@
Import-Module "$PSScriptRoot\..\GPoZaurr.psd1" -Force
# Find broken GPOs
Get-GPOZaurrBroken -Verbose -IncludeDomains 'ad.evotec.pl' | Format-Table
Get-GPOZaurrBroken -Verbose | Format-Table # -IncludeDomains 'ad.evotec.pl' | Format-Table
# this allows you to process X amount of orphaned folders/files (good for testing)
Remove-GPOZaurrBroken -Verbose -WhatIf #-IncludeDomains 'ad.evotec.pl' #-LimitProcessing 2
Remove-GPOZaurrBroken -Verbose -WhatIf -Type AD, SYSVOL -LimitProcessing 1 #-IncludeDomains 'ad.evotec.pl' #-LimitProcessing 2
# this runs for whole SYSVOL and checks things against GPOS
Remove-GPOZaurrBroken -Verbose -IncludeDomains 'ad.evotec.xyz' -BackupPath $Env:UserProfile\Desktop\MyBackup1 -WhatIf
Remove-GPOZaurrBroken -Verbose -IncludeDomains 'ad.evotec.xyz' -BackupPath $Env:UserProfile\Desktop\MyBackup1 -WhatIf -Type AD, SYSVOL
+1 -1
View File
@@ -8,7 +8,7 @@ Import-Module "$PSScriptRoot\..\GPoZaurr.psd1" -Force
#Get-GPOZaurrLinkSummary -Report 'LinksSummary' -UnlimitedProperties | Format-Table *
$Report = Get-GPOZaurrLinkSummary #-UnlimitedProperties
$Report | Format-Table *
$Report | Format-Table
$Report.MultipleLinks | Format-Table *
$Report.OneLink | Format-Table *
$Report.LinksSummary | Format-Table *
+1 -1
View File
@@ -5,7 +5,7 @@ $Types = @(
@{ Name = 'GPOConsistency'; Path = "$PSScriptRoot\Reports\GPOConsistency.html" }
@{ Name = 'GPODuplicates'; Path = "$PSScriptRoot\Reports\GPODuplicates.html" }
@{ Name = 'GPOList'; Path = "$PSScriptRoot\Reports\GPOList.html" }
@{ Name = 'GPOOrphans'; Path = "$PSScriptRoot\Reports\GPOOrphans.html" }
@{ Name = 'GPOBroken'; Path = "$PSScriptRoot\Reports\GPOOrphans.html" }
@{ Name = 'GPOPassword'; Path = "$PSScriptRoot\Reports\GPOPassword.html" }
@{ Name = 'NetLogonPermissions'; Path = "$PSScriptRoot\Reports\NetLogonPermissions.html" }
)
+3 -3
View File
@@ -4,11 +4,11 @@
CmdletsToExport = @()
CompanyName = 'Evotec'
CompatiblePSEditions = @('Desktop')
Copyright = '(c) 2011 - 2020 Przemyslaw Klys @ Evotec. All rights reserved.'
Copyright = '(c) 2011 - 2021 Przemyslaw Klys @ Evotec. All rights reserved.'
Description = 'Group Policy Eater is a PowerShell module that aims to gather information about Group Policies but also allows fixing issues that you may find in them.'
FunctionsToExport = @('Add-GPOPermission', 'Add-GPOZaurrPermission', 'Backup-GPOZaurr', 'Clear-GPOZaurrSysvolDFSR', 'ConvertFrom-CSExtension', 'Find-CSExtension', 'Get-GPOZaurr', 'Get-GPOZaurrAD', 'Get-GPOZaurrBackupInformation', 'Get-GPOZaurrBroken', 'Get-GPOZaurrDictionary', 'Get-GPOZaurrDuplicateObject', 'Get-GPOZaurrFiles', 'Get-GPOZaurrFilesPolicyDefinition', 'Get-GPOZaurrFolders', 'Get-GPOZaurrInheritance', 'Get-GPOZaurrLegacyFiles', 'Get-GPOZaurrLink', 'Get-GPOZaurrLinkSummary', 'Get-GPOZaurrNetLogon', 'Get-GPOZaurrOwner', 'Get-GPOZaurrPassword', 'Get-GPOZaurrPermission', 'Get-GPOZaurrPermissionAnalysis', 'Get-GPOZaurrPermissionConsistency', 'Get-GPOZaurrPermissionIssue', 'Get-GPOZaurrPermissionRoot', 'Get-GPOZaurrPermissionSummary', 'Get-GPOZaurrSysvolDFSR', 'Get-GPOZaurrWMI', 'Invoke-GPOZaurr', 'Invoke-GPOZaurrContent', 'Invoke-GPOZaurrPermission', 'Invoke-GPOZaurrSupport', 'New-GPOZaurrWMI', 'Optimize-GPOZaurr', 'Remove-GPOPermission', 'Remove-GPOZaurr', 'Remove-GPOZaurrBroken', 'Remove-GPOZaurrDuplicateObject', 'Remove-GPOZaurrFolders', 'Remove-GPOZaurrLegacyFiles', 'Remove-GPOZaurrPermission', 'Remove-GPOZaurrWMI', 'Repair-GPOZaurrNetLogonOwner', 'Repair-GPOZaurrPermission', 'Repair-GPOZaurrPermissionConsistency', 'Restore-GPOZaurr', 'Save-GPOZaurrFiles', 'Set-GPOOwner', 'Set-GPOZaurrOwner', 'Set-GPOZaurrStatus', 'Skip-GroupPolicy')
FunctionsToExport = @('Add-GPOPermission', 'Add-GPOZaurrPermission', 'Backup-GPOZaurr', 'Clear-GPOZaurrSysvolDFSR', 'ConvertFrom-CSExtension', 'Find-CSExtension', 'Get-GPOZaurr', 'Get-GPOZaurrAD', 'Get-GPOZaurrBackupInformation', 'Get-GPOZaurrBroken', 'Get-GPOZaurrBrokenLink', 'Get-GPOZaurrDictionary', 'Get-GPOZaurrDuplicateObject', 'Get-GPOZaurrFiles', 'Get-GPOZaurrFilesPolicyDefinition', 'Get-GPOZaurrFolders', 'Get-GPOZaurrInheritance', 'Get-GPOZaurrLegacyFiles', 'Get-GPOZaurrLink', 'Get-GPOZaurrLinkSummary', 'Get-GPOZaurrNetLogon', 'Get-GPOZaurrOwner', 'Get-GPOZaurrPassword', 'Get-GPOZaurrPermission', 'Get-GPOZaurrPermissionAnalysis', 'Get-GPOZaurrPermissionConsistency', 'Get-GPOZaurrPermissionIssue', 'Get-GPOZaurrPermissionRoot', 'Get-GPOZaurrPermissionSummary', 'Get-GPOZaurrSysvolDFSR', 'Get-GPOZaurrWMI', 'Invoke-GPOZaurr', 'Invoke-GPOZaurrContent', 'Invoke-GPOZaurrPermission', 'Invoke-GPOZaurrSupport', 'New-GPOZaurrWMI', 'Optimize-GPOZaurr', 'Remove-GPOPermission', 'Remove-GPOZaurr', 'Remove-GPOZaurrBroken', 'Remove-GPOZaurrDuplicateObject', 'Remove-GPOZaurrFolders', 'Remove-GPOZaurrLegacyFiles', 'Remove-GPOZaurrPermission', 'Remove-GPOZaurrWMI', 'Repair-GPOZaurrBrokenLink', 'Repair-GPOZaurrNetLogonOwner', 'Repair-GPOZaurrPermission', 'Repair-GPOZaurrPermissionConsistency', 'Restore-GPOZaurr', 'Save-GPOZaurrFiles', 'Set-GPOOwner', 'Set-GPOZaurrOwner', 'Set-GPOZaurrStatus', 'Skip-GroupPolicy')
GUID = 'f7d4c9e4-0298-4f51-ad77-e8e3febebbde'
ModuleVersion = '0.0.100'
ModuleVersion = '0.0.103'
PowerShellVersion = '5.1'
PrivateData = @{
PSData = @{
+39
View File
@@ -0,0 +1,39 @@
function Get-GPOPrivInheritance {
[cmdletBinding()]
param(
[parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
[System.Collections.IDictionary] $CacheReturnedGPOs,
[System.Collections.IDictionary] $ForestInformation,
[string] $Domain,
[switch] $SkipDomainRoot,
[switch] $SkipDomainControllers
)
foreach ($Object in $ADObject) {
if ($SkipDomainRoot) {
if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) {
# other skips Domain Root
continue
}
}
if ($SkipDomainControllers) {
if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']) {
# other skips Domain Controllers
continue
}
}
$Inheritance = Get-GPInheritance -Target $Object.DistinguishedName
foreach ($Link in $Inheritance.GpoLinks) {
[PSCustomObject] @{
DisplayName = $Link.DisplayName
DomainName = $Domain
GUID = $Link.GPOID
Enabled = $Link.Enabled
Enforced = $Link.Enforced
Order = $Link.Order
Target = $Object.DistinguishedName
TargetCanonical = $Object.CanonicalName
TargetObjectClass = $Object.objectClass
}
}
}
}
+92
View File
@@ -0,0 +1,92 @@
function Get-GPOPrivInheritanceLoop {
[cmdletBinding()]
param(
[Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
[System.Collections.IDictionary] $CacheReturnedGPOs,
[System.Collections.IDictionary] $ForestInformation,
[validateset('Root', 'DomainControllers', 'OrganizationalUnit')][string[]] $Linked,
[string] $SearchBase,
[Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope,
[string] $Filter
)
if (-not $ADObject) {
if ($Linked) {
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 'DomainControllers') {
$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-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation
}
if ($Linked -contains 'Root') {
$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-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation
}
if ($Linked -contains 'Site') {
# Sites are defined only in primary domain
# Sites are not supported by Get-GPInheritance
}
if ($Linked -contains 'OrganizationalUnit') {
$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-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -SkipDomainRoot -SkipDomainControllers
}
}
} 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-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation
}
}
} else {
Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObject -Domain '' -ForestInformation $ForestInformation
}
}
+39
View File
@@ -0,0 +1,39 @@
function Get-GPOPrivLink {
[cmdletBinding()]
param(
[parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
[System.Collections.IDictionary] $CacheReturnedGPOs,
[System.Collections.IDictionary] $ForestInformation,
[string] $Domain,
[switch] $SkipDomainRoot,
[switch] $SkipDomainControllers,
[switch] $AsHashTable,
[switch] $SkipDuplicates
)
foreach ($Object in $ADObject) {
if ($SkipDomainRoot) {
if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) {
# other skips Domain Root
continue
}
}
if ($SkipDomainControllers) {
if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']) {
# other skips Domain Controllers
continue
}
}
$OutputGPOs = Get-PrivGPOZaurrLink -Object $Object -Limited:$Limited.IsPresent -GPOCache $GPOCache
foreach ($OutputGPO in $OutputGPOs) {
if (-not $SkipDuplicates) {
$OutputGPO
} else {
$UniqueGuid = -join ($OutputGPO.DomainName, $OutputGPO.Guid)
if (-not $CacheReturnedGPOs[$UniqueGuid]) {
$CacheReturnedGPOs[$UniqueGuid] = $OutputGPO
$OutputGPO
}
}
}
}
}
+197
View File
@@ -0,0 +1,197 @@
function Get-GPOZaurrLinkInheritance {
<#
.SYNOPSIS
Short description
.DESCRIPTION
Long description
.PARAMETER ADObject
Parameter description
.PARAMETER Filter
Parameter description
.PARAMETER SearchBase
Parameter description
.PARAMETER SearchScope
Parameter description
.PARAMETER Linked
Parameter description
.PARAMETER Limited
Parameter description
.PARAMETER SkipDuplicates
Parameter description
.PARAMETER GPOCache
Parameter description
.PARAMETER Forest
Parameter description
.PARAMETER ExcludeDomains
Parameter description
.PARAMETER IncludeDomains
Parameter description
.PARAMETER ExtendedForestInformation
Parameter description
.PARAMETER AsHashTable
Parameter description
.PARAMETER Summary
Parameter description
.EXAMPLE
$Output = Get-GPOZaurrLinkInheritance -Summary
$Output | Format-Table
$Output[5]
$Output[5].Links | Format-Table
$Output[5].LinksObjects | Format-Table
.NOTES
This is based on Get-GPInheritance which isn't ideal and doesn't support sites. Get-GPOZaurrLink is better. Leaving in case I need it later on for private use only.
#>
[cmdletbinding(DefaultParameterSetName = 'All')]
param(
[parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
# weirdly enough site doesn't really work this way unless you give it 'CN=Configuration,DC=ad,DC=evotec,DC=xyz' as SearchBase
[parameter(ParameterSetName = 'Filter')][string] $Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')",
[parameter(ParameterSetName = 'Filter')][string] $SearchBase,
[parameter(ParameterSetName = 'Filter')][Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope,
[parameter(ParameterSetName = 'Linked', Mandatory)][validateset('Root', 'DomainControllers', 'OrganizationalUnit')][string[]] $Linked,
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[switch] $Limited,
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[switch] $SkipDuplicates,
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[System.Collections.IDictionary] $GPOCache,
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[alias('ForestName')][string] $Forest,
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[string[]] $ExcludeDomains,
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[alias('Domain', 'Domains')][string[]] $IncludeDomains,
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[System.Collections.IDictionary] $ExtendedForestInformation,
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[switch] $AsHashTable,
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[switch] $Summary
)
Begin {
$CacheReturnedGPOs = [ordered] @{}
$ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
if (-not $GPOCache -and -not $Limited) {
$GPOCache = @{ }
# While initially we used $ForestInformation.Domains but the thing is GPOs can be linked to other domains so we need to get them all so we can use cache of it later on even if we're processing just one domain
# That's why we use $ForestInformation.Forest.Domains instead
foreach ($Domain in $ForestInformation.Forest.Domains) {
$QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
Get-GPO -All -DomainName $Domain -Server $QueryServer | ForEach-Object {
$GPOCache["$Domain$($_.ID.Guid)"] = $_
}
}
}
}
Process {
if (-not $Filter -and -not $Linked) {
# We choose ALL, except SITE which is not supported gor Get-GPInheritance
# that's why it's better to use Get-GPOZaurrLink
#$Linked = 'Root', 'DomainControllers', 'Site', 'OrganizationalUnit'
}
$getGPOPrivInheritanceLoopSplat = @{
Linked = $Linked
ForestInformation = $ForestInformation
CacheReturnedGPOs = $CacheReturnedGPOs
SearchScope = $SearchScope
SearchBase = $SearchBase
ADObject = $ADObject
Filter = $Filter
}
Remove-EmptyValue -Hashtable $getGPOPrivInheritanceLoopSplat -Recursive
# we need to use nested functions to support pipeline output and as hashtable and reporting that returns single value
if ($AsHashTable -or $Summary) {
$HashTable = [ordered] @{}
$SummaryHashtable = [ordered] @{}
$Links = Get-GPOPrivInheritanceLoop @getGPOPrivInheritanceLoopSplat
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.Target
LinksObjects = $Link
}
$SummaryHashtable[$Key] = $SummaryLink
}
if ($AsHashTable -and $Summary) {
$SummaryHashtable
} elseif ($AsHashTable) {
$HashTable
} elseif ($Summary) {
$SummaryHashtable.Values
}
} else {
Get-GPOPrivInheritanceLoop @getGPOPrivInheritanceLoopSplat
}
}
End {
}
}
+175
View File
@@ -0,0 +1,175 @@
function Get-GPOZaurrLinkLoop {
[cmdletBinding()]
param(
[Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
[System.Collections.IDictionary] $CacheReturnedGPOs,
[System.Collections.IDictionary] $ForestInformation,
[validateset('All', 'Root', 'DomainControllers', 'Site', 'OrganizationalUnit')][string[]] $Linked,
[string] $SearchBase,
[Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope,
[string] $Filter,
[switch] $SkipDuplicates,
[string[]] $Site
)
if (-not $ADObject) {
if ($Site) {
foreach ($S in $Site) {
foreach ($Domain in $ForestInformation.Domains) {
Write-Verbose "Get-GPOZaurrLink - Getting GPO links for site $Site"
# Sites are defined only in primary domain
if ($ForestInformation['DomainsExtended'][$Domain]['DNSRoot'] -eq $ForestInformation['DomainsExtended'][$Domain]['Forest']) {
$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]
}
$Splat['Filter'] = "(objectClass -eq 'site') -and (name -eq '$S')"
$Splat['SearchBase'] = -join ("CN=Configuration,", $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'])
try {
$ADObjectGPO = Get-ADObject @Splat
} catch {
Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
}
if ($ADObjectGPO) {
Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
}
}
}
}
} elseif ($SearchBase -or $SearchScope -or $Filter) {
foreach ($Domain in $ForestInformation.Domains) {
if (-not $Filter) {
$Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')"
}
$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)"
}
if ($ADObjectGPO) {
Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
}
}
} elseif (-not $Filter) {
# if not linked, we force it to All
if (-not $Linked) {
$Linked = 'All'
}
foreach ($Domain in $ForestInformation.Domains) {
Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain"
$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') {
Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at ROOT level"
$Splat['Filter'] = "objectClass -eq 'domainDNS'"
$Splat['SearchBase'] = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
try {
$ADObjectGPO = Get-ADObject @Splat
} catch {
Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
}
if ($ADObjectGPO) {
Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
}
}
if ($Linked -contains 'Site' -or $Linked -contains 'All') {
Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at SITE level"
# Sites are defined only in primary domain
if ($ForestInformation['DomainsExtended'][$Domain]['DNSRoot'] -eq $ForestInformation['DomainsExtended'][$Domain]['Forest']) {
$Splat['Filter'] = "(objectClass -eq 'site')"
$Splat['SearchBase'] = -join ("CN=Configuration,", $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'])
try {
$ADObjectGPO = Get-ADObject @Splat
} catch {
Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
}
if ($ADObjectGPO) {
Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
}
}
}
if ($Linked -contains 'DomainControllers' -or $Linked -contains 'All') {
Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at DC level"
$Splat['Filter'] = "(objectClass -eq 'organizationalUnit')"
$Splat['SearchBase'] = $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']
try {
$ADObjectGPO = Get-ADObject @Splat
} catch {
Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
}
if ($ADObjectGPO) {
Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
}
}
if ($Linked -contains 'OrganizationalUnit' -or $Linked -contains 'All') {
Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at OU level"
$Splat['Filter'] = "(objectClass -eq 'organizationalUnit')"
$Splat['SearchBase'] = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
try {
$ADObjectGPO = Get-ADObject @Splat
} catch {
Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
}
if ($ADObjectGPO) {
Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -SkipDomainRoot -SkipDomainControllers -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
}
}
}
}
<#
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)"
}
if ($ADObjectGPO) {
Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
}
}
}
#>
} else {
Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObject -Domain '' -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
}
}
+61 -60
View File
@@ -6,69 +6,70 @@
[System.Collections.IDictionary] $GPOCache
)
if ($Object.GpLink -and $Object.GpLink.Trim() -ne '') {
#$Object.GpLink -split { $_ -eq '[' -or $_ -eq ']' } -replace ';0' -replace 'LDAP://'
$Object.GpLink -split '\[LDAP://' -split ';' | ForEach-Object -Process {
#Write-Verbose $_
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['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 $_ -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
}
}
}
$ObjectsToProcess = $Object.GpLink -split '\]\['
} 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."
}
$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"
} else {
$Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $_ -ToDC
$Output['GPODistinguishedName'] = $_
[PSCustomObject] $Output
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
# This is object name, usually used for sites
$Output['Name'] = $Object.Name
[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 $DN -ToDC
$Output['GPODistinguishedName'] = $DN
[PSCustomObject] $Output
}
}
}
+55 -31
View File
@@ -9,25 +9,63 @@
[string] $Splitter = [System.Environment]::NewLine,
[switch] $ReturnObject,
[System.Collections.IDictionary] $ExcludeGroupPolicies,
[string[]] $Type
[string[]] $Type,
[System.Collections.IDictionary] $LinksSummaryCache
)
if ($XMLContent.GPO.LinksTo) {
$LinkSplit = ([Array] $XMLContent.GPO.LinksTo).Where( { $_.Enabled -eq $true }, 'Split')
[Array] $LinksEnabled = $LinkSplit[0]
[Array] $LinksDisabled = $LinkSplit[1]
$LinksEnabledCount = $LinksEnabled.Count
$LinksDisabledCount = $LinksDisabled.Count
$LinksTotalCount = ([Array] $XMLContent.GPO.LinksTo).Count
if ($LinksEnabledCount -eq 0) {
$Linked = $false
if ($LinksSummaryCache) {
$SearchGUID = -join ($XMLContent.GPO.Identifier.Domain.'#text', $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}')
if ($LinksSummaryCache[$SearchGUID]) {
$Linked = $LinksSummaryCache[$SearchGUID].Linked
$LinksEnabledCount = $LinksSummaryCache[$SearchGUID].LinksEnabledCount
$LinksDisabledCount = $LinksSummaryCache[$SearchGUID].LinksDisabledCount
$LinksTotalCount = $LinksSummaryCache[$SearchGUID].LinksCount
$Links = $LinksSummaryCache[$SearchGUID].Links
$LinksObjects = $LinksSummaryCache[$SearchGUID].LinksObjects
} else {
$Linked = $true
$Linked = $false
$LinksEnabledCount = 0
$LinksDisabledCount = 0
$LinksTotalCount = 0
$Links = $null
$LinksObjects = $null
}
} else {
$Linked = $false
$LinksEnabledCount = 0
$LinksDisabledCount = 0
$LinksTotalCount = 0
if ($XMLContent.GPO.LinksTo) {
$LinkSplit = ([Array] $XMLContent.GPO.LinksTo).Where( { $_.Enabled -eq $true }, 'Split')
[Array] $LinksEnabled = $LinkSplit[0]
[Array] $LinksDisabled = $LinkSplit[1]
$LinksEnabledCount = $LinksEnabled.Count
$LinksDisabledCount = $LinksDisabled.Count
$LinksTotalCount = ([Array] $XMLContent.GPO.LinksTo).Count
if ($LinksEnabledCount -eq 0) {
$Linked = $false
} else {
$Linked = $true
}
$Links = @(
$XMLContent.GPO.LinksTo | ForEach-Object -Process {
if ($_) {
$_.SOMPath
}
}
) -join $Splitter
$LinksObjects = $XMLContent.GPO.LinksTo | ForEach-Object -Process {
if ($_) {
[PSCustomObject] @{
CanonicalName = $_.SOMPath
Enabled = $_.Enabled
NoOverride = $_.NoOverride
}
}
}
} else {
$Linked = $false
$LinksEnabledCount = 0
$LinksDisabledCount = 0
$LinksTotalCount = 0
$Links = $null
$LinksObjects = $null
}
}
# Find proper values for enabled/disabled user/computer settings
if ($XMLContent.GPO.Computer.Enabled -eq 'False') {
@@ -281,22 +319,8 @@
}
)
'Auditing' = if ($XMLContent.GPO.SecurityDescriptor.AuditingPresent.'#text' -eq 'true') { $true } else { $false }
'Links' = @(
$XMLContent.GPO.LinksTo | ForEach-Object -Process {
if ($_) {
$_.SOMPath
}
}
) -join $Splitter
'LinksObjects' = $XMLContent.GPO.LinksTo | ForEach-Object -Process {
if ($_) {
[PSCustomObject] @{
CanonicalName = $_.SOMPath
Enabled = $_.Enabled
NoOverride = $_.NoOverride
}
}
}
'Links' = $Links
'LinksObjects' = $LinksObjects
'GPOObject' = $GPO
}
if ($GPOOutput.ACL) {
+148
View File
@@ -0,0 +1,148 @@
$GPOZaurrBrokenLink = [ordered] @{
Name = 'Group Policy Broken Links'
Enabled = $true
ActionRequired = $null
Data = $null
Execute = {
Get-GPOZaurrBrokenLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
}
Processing = {
$Script:Reporting['GPOBrokenLink']['Variables']['RequireDeletion'] = $Script:Reporting['GPOBrokenLink']['Data'].Count
$Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'] = @{}
$Script:Reporting['GPOBrokenLink']['Variables']['Unique'] = @{}
foreach ($Link In $Script:Reporting['GPOBrokenLink']['Data']) {
$DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Link.DistinguishedName
# Create Per Domain Variables
if (-not $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$DomainName]) {
$Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$DomainName] = 0
}
$Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$DomainName]++
# Lets do unique OU counting
$Script:Reporting['GPOBrokenLink']['Variables']['Unique'][$Link.CanonicalName] = $Link
}
$Script:Reporting['GPOBrokenLink']['Variables']['UniqueObjects'] = $Script:Reporting['GPOBrokenLink']['Variables']['Unique'].Keys
if ($Script:Reporting['GPOBrokenLink']['Data'].Count -gt 0) {
$Script:Reporting['GPOBrokenLink']['ActionRequired'] = $true
} else {
$Script:Reporting['GPOBrokenLink']['ActionRequired'] = $false
}
}
Variables = @{
RequireDeletion = 0
WillFixPerDomain = $null
UniqueObjects = $null
Unique = $null
}
Overview = {
}
Summary = {
New-HTMLText -FontSize 10pt -TextBlock {
"When GPO is deleted in a proper way it usually is removed from AD, SYSVOL and any link to it is also discarded. "
"Unfortunetly this is true only if the GPO is created and linked within same domain. "
"If GPO is linked in another domain, this leaves a broken link hanging on wherever it was linked before. "
}
New-HTMLText -Text @(
'As it stands currently there are ',
$Script:Reporting['GPOBrokenLink']['Data'].Count,
' broken links that need to be deleted over '
$Script:Reporting['GPOBrokenLink']['Variables']['UniqueObjects'].Count,
' unique objects. '
) -FontSize 10pt -FontWeight normal, bold, normal, bold, normal
New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
New-HTMLList -Type Unordered {
foreach ($Domain in $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'].Keys) {
New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
}
} -FontSize 10pt
}
Solution = {
New-HTMLSection -Invisible {
New-HTMLPanel {
& $Script:GPOConfiguration['GPOBrokenLink']['Summary']
}
New-HTMLPanel {
New-HTMLChart {
New-ChartLegend -Names 'Bad' -Color Salmon
New-ChartBar -Name 'Broken Links' -Value $Script:Reporting['GPOBrokenLink']['Data'].Count
} -Title 'Broken Links' -TitleAlignment center
}
}
New-HTMLSection -Name 'Group Policy Broken Links' {
New-HTMLTable -DataTable $Script:Reporting['GPOBrokenLink']['Data'] -Filtering {
} -PagingOptions 10, 20, 30, 40, 50
}
if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
New-HTMLSection -Name 'Steps to remove Broken Links' {
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 GPOZaurr -Force
Import-Module GPOZaurr -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 report' {
New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding fixing GPO links. To generate new report please use:"
New-HTMLCodeBlock -Code {
Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBrokenLinkBefore.html -Verbose -Type GPOBrokenLink
}
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 {
$GPOOutput = Get-GPOZaurrBrokenLink -Verbose
$GPOOutput | Format-Table * # do your actions as desired
}
New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you."
}
New-HTMLWizardStep -Name 'Remove Broken Links' {
New-HTMLText -Text "Following command when executed, runs internally command that lists all broken links. After finding them all it delets them according to given criteria. "
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 {
Repair-GPOZaurrBrokenLink -WhatIf -Verbose
}
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. Once happy with results please follow with command: "
}
New-HTMLCodeBlock -Code {
Repair-GPOZaurrBrokenLink -Verbose -LimitProcessing 2
}
New-HTMLText -TextBlock {
"This command when executed removes only first X number of links. Keep in mind that 5 broken links on a single Organizational Unit are treated as one. Use LimitProcessing parameter to prevent mass delete 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-HTMLWizardStep -Name 'Verification report' {
New-HTMLText -TextBlock {
"Once cleanup task was executed properly, we need to verify that report now shows no problems."
}
New-HTMLCodeBlock -Code {
Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBrokenLinkAfter.html -Verbose -Type GPOBrokenLink
}
New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond
}
} -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
}
}
}
}
if ($Script:Reporting['GPOBrokenLink']['WarningsAndErrors']) {
New-HTMLSection -Name 'Warnings & Errors to Review' {
New-HTMLTable -DataTable $Script:Reporting['GPOBrokenLink']['WarningsAndErrors'] -Filtering {
New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
}
}
}
}
}
+1 -1
View File
@@ -68,7 +68,7 @@
New-HTMLCodeBlock -Code {
Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrDuplicateObjectsBefore.html -Verbose -Type GPODuplicates
}
New-HTMLText -Text {
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. "
}
+29
View File
@@ -0,0 +1,29 @@
$GPOZaurrLinks = [ordered] @{
Name = 'Group Policy Links'
Enabled = $true
ActionRequired = $null
Data = $null
Execute = {
Get-GPOZaurrLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Summary
}
Processing = {
}
Variables = @{
}
Overview = {
}
Solution = {
New-HTMLTable -DataTable $Script:Reporting['GPOLinks']['Data'] -Filtering
if ($Script:Reporting['GPOLinks']['WarningsAndErrors']) {
New-HTMLSection -Name 'Warnings & Errors to Review' {
New-HTMLTable -DataTable $Script:Reporting['GPOLinks']['WarningsAndErrors'] -Filtering {
New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
}
}
}
}
}
+92 -41
View File
@@ -1,5 +1,5 @@
$GPOZaurrOrphans = [ordered] @{
Name = 'Orphaned Group Policies'
Name = 'Broken Group Policies'
Enabled = $true
ActionRequired = $null
Data = $null
@@ -7,41 +7,52 @@
Get-GPOZaurrBroken -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
}
Processing = {
$Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'] = @{}
$Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssuePerDomain'] = @{}
foreach ($GPO in $Script:Reporting['GPOOrphans']['Data']) {
if (-not $Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]) {
$Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName] = 0
$Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'] = @{}
$Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'] = @{}
$Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'] = @{}
foreach ($GPO in $Script:Reporting['GPOBroken']['Data']) {
if (-not $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]) {
$Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName] = 0
}
if (-not $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName]) {
$Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName] = 0
if (-not $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName]) {
$Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName] = 0
}
if (-not $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'][$GPO.DomainName]) {
$Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'][$GPO.DomainName] = 0
}
if ($GPO.Status -eq 'Not available in AD') {
$Script:Reporting['GPOOrphans']['Variables']['NotAvailableInAD']++
$Script:Reporting['GPOOrphans']['Variables']['ToBeDeleted']++
$Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++
$Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD']++
$Script:Reporting['GPOBroken']['Variables']['ToBeDeleted']++
$Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++
} elseif ($GPO.Status -eq 'Not available on SYSVOL') {
$Script:Reporting['GPOOrphans']['Variables']['NotAvailableOnSysvol']++
$Script:Reporting['GPOOrphans']['Variables']['ToBeDeleted']++
$Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++
$Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol']++
$Script:Reporting['GPOBroken']['Variables']['ToBeDeleted']++
$Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++
} elseif ($GPO.Status -eq 'Permissions issue') {
$Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssue']++
$Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName]++
$Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue']++
$Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName]++
} elseif ($GPO.Status -eq 'ObjectClass issue') {
$Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssue']++
$Script:Reporting['GPOBroken']['Variables']['ToBeDeleted']++
$Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'][$GPO.DomainName]++
$Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++
}
}
if ($Script:Reporting['GPOOrphans']['Variables']['ToBeDeleted'] -gt 0) {
$Script:Reporting['GPOOrphans']['ActionRequired'] = $true
if ($Script:Reporting['GPOBroken']['Variables']['ToBeDeleted'] -gt 0) {
$Script:Reporting['GPOBroken']['ActionRequired'] = $true
} else {
$Script:Reporting['GPOOrphans']['ActionRequired'] = $false
$Script:Reporting['GPOBroken']['ActionRequired'] = $false
}
}
Variables = @{
NotAvailableInAD = 0
NotAvailableOnSysvol = 0
NotAvailablePermissionIssue = 0
NotAvailablePermissionIssuePerDomain = $null
ToBeDeleted = 0
ToBeDeletedPerDomain = $null
NotAvailableInAD = 0
NotAvailableOnSysvol = 0
NotAvailablePermissionIssue = 0
NotAvailablePermissionIssuePerDomain = $null
ToBeDeleted = 0
ToBeDeletedPerDomain = $null
NotAvailableObjectClassIssue = 0
NotAvailableObjectClassIssuePerDomain = $null
}
Overview = {
<#
@@ -58,15 +69,15 @@
}
New-HTMLText -Text 'Following chart presents ', 'Broken / Orphaned Group Policies' -FontSize 10pt -FontWeight normal, bold
New-HTMLList -Type Unordered {
New-HTMLListItem -Text 'Group Policies on SYSVOL, but no details in AD: ', $Script:Reporting['GPOOrphans']['Variables']['NotAvailableInAD'] -FontWeight normal, bold
New-HTMLListItem -Text 'Group Policies in AD, but no content on SYSVOL: ', $Script:Reporting['GPOOrphans']['Variables']['NotAvailableOnSysvol'] -FontWeight normal, bold
New-HTMLListItem -Text "Group Policies which couldn't be assed due to permissions issue: ", $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssue'] -FontWeight normal, bold
New-HTMLListItem -Text 'Group Policies on SYSVOL, but no details in AD: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD'] -FontWeight normal, bold
New-HTMLListItem -Text 'Group Policies in AD, but no content on SYSVOL: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol'] -FontWeight normal, bold
New-HTMLListItem -Text "Group Policies which couldn't be assed due to permissions issue: ", $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue'] -FontWeight normal, bold
} -FontSize 10pt
New-HTMLText -FontSize 10pt -Text 'Those problems must be resolved before doing other clenaup activities.'
New-HTMLChart {
New-ChartBarOptions -Type barStacked
New-ChartLegend -Name 'Not in AD', 'Not on SYSVOL', 'Permissions Issue' -Color Crimson, LightCoral, IndianRed
New-ChartBar -Name 'Orphans' -Value $Script:Reporting['GPOOrphans']['Variables']['NotAvailableInAD'], $Script:Reporting['GPOOrphans']['Variables']['NotAvailableOnSysvol'], $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssue']
New-ChartBar -Name 'Orphans' -Value $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD'], $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol'], $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue']
} -Title 'Broken / Orphaned Group Policies' -TitleAlignment center
}
#>
@@ -85,14 +96,15 @@
} -FontSize 10pt
New-HTMLText -Text 'Following problems were detected:' -FontSize 10pt -FontWeight bold
New-HTMLList -Type Unordered {
New-HTMLListItem -Text 'Group Policies on SYSVOL, but no details in AD: ', $Script:Reporting['GPOOrphans']['Variables']['NotAvailableInAD'] -FontWeight normal, bold
New-HTMLListItem -Text 'Group Policies in AD, but no content on SYSVOL: ', $Script:Reporting['GPOOrphans']['Variables']['NotAvailableOnSysvol'] -FontWeight normal, bold
New-HTMLListItem -Text "Group Policies which couldn't be assed due to permissions issue: ", $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssue'] -FontWeight normal, bold
New-HTMLListItem -Text 'Group Policies on SYSVOL, but no details in AD: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD'] -FontWeight normal, bold
New-HTMLListItem -Text 'Group Policies in AD, but no content on SYSVOL: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol'] -FontWeight normal, bold
New-HTMLListItem -Text 'Group Policies which exists, but have wrong ObjectClass: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssue'] -FontWeight normal, bold
New-HTMLListItem -Text "Group Policies which couldn't be assed due to permissions issue: ", $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue'] -FontWeight normal, bold
} -FontSize 10pt
New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
New-HTMLList -Type Unordered {
foreach ($Domain in $Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'].Keys) {
New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
foreach ($Domain in $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'].Keys) {
New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
}
} -FontSize 10pt
New-HTMLText -Text "Please review output in table and follow the steps below table to get Active Directory Group Policies in healthy state." -FontSize 10pt
@@ -100,25 +112,26 @@
Solution = {
New-HTMLSection -Invisible {
New-HTMLPanel {
& $Script:GPOConfiguration['GPOOrphans']['Summary']
& $Script:GPOConfiguration['GPOBroken']['Summary']
}
New-HTMLPanel {
New-HTMLChart {
New-ChartBarOptions -Type barStacked
New-ChartLegend -Name 'Not in AD', 'Not on SYSVOL', 'Permissions Issue' -Color Crimson, LightCoral, IndianRed
New-ChartBar -Name 'Orphans' -Value $Script:Reporting['GPOOrphans']['Variables']['NotAvailableInAD'], $Script:Reporting['GPOOrphans']['Variables']['NotAvailableOnSysvol'], $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssue']
New-ChartLegend -Name 'Not in AD', 'Not on SYSVOL', 'ObjectClass Issue', 'Permissions Issue' -Color Crimson, LightCoral, MediumOrchid, IndianRed
New-ChartBar -Name 'Broken' -Value $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD'], $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol'], $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssue'], $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue']
} -Title 'Broken / Orphaned Group Policies' -TitleAlignment center
}
}
New-HTMLSection -Name 'Health State of Group Policies' {
New-HTMLTable -DataTable $Script:Reporting['GPOOrphans']['Data'] -Filtering {
New-HTMLTable -DataTable $Script:Reporting['GPOBroken']['Data'] -Filtering {
New-HTMLTableCondition -Name 'Status' -Value "Not available in AD" -BackgroundColor Salmon -ComparisonType string
New-HTMLTableCondition -Name 'Status' -Value "Not available on SYSVOL" -BackgroundColor LightCoral -ComparisonType string
New-HTMLTableCondition -Name 'Status' -Value "ObjectClass issue" -BackgroundColor MediumOrchid -ComparisonType string
New-HTMLTableCondition -Name 'Status' -Value "Permissions issue" -BackgroundColor MediumVioletRed -ComparisonType string -Color White
} -PagingOptions 10, 20, 30, 40, 50
}
if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
New-HTMLSection -Name 'Steps to fix - Not available on SYSVOL / Active Directory' {
New-HTMLSection -Name 'Steps to fix - Not available on SYSVOL / Active Directory / ObjectClass issue' {
New-HTMLContainer {
New-HTMLSpanStyle -FontSize 10pt {
New-HTMLWizard {
@@ -243,6 +256,44 @@
"If there's nothing else to be deleted on AD side, we can skip to next step step. "
}
}
New-HTMLWizardStep -Name 'Fix GPOs of wrong ObjectClass' {
New-HTMLText -Text @(
"Following command when executed runs cleanup procedure that removes all GPOs which have ObjectClass of Container, rather than required groupPolicyContainer. "
"Make sure when running it for the first time to run it with ",
"WhatIf",
" parameter as shown below to prevent accidental removal."
'When run it will remove GPO metadata from AD, and any files/folders from SYSVOL.'
) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black
New-HTMLCodeBlock -Code {
Remove-GPOZaurrBroken -Type ObjectClass -WhatIf -Verbose
}
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 {
Remove-GPOZaurrBroken -Type ObjectClass -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose
}
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 deleted matches expected data. "
"Keep in mind that there is no backup for this as backup process doesn't see GPOs that are of wrong ObjectClass. "
"Once you delete it, it's gone. "
} -LineBreak
New-HTMLText -Text 'Once happy with results please follow with command (this will start deletion process): ' -LineBreak -FontWeight bold
New-HTMLCodeBlock -Code {
Remove-GPOZaurrBroken -Type ObjectClass -LimitProcessing 2 -Verbose
}
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 {
Remove-GPOZaurrBroken -Type ObjectClass -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose
}
New-HTMLText -TextBlock {
"This command when executed deletes only first X broken GPOs. Use LimitProcessing parameter to prevent mass delete 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. "
"If there's nothing else to be deleted, we can skip to next step step. "
}
}
New-HTMLWizardStep -Name 'Verification report' {
New-HTMLText -TextBlock {
"Once cleanup task was executed properly, we need to verify that report now shows no problems."
@@ -257,9 +308,9 @@
}
}
}
if ($Script:Reporting['GPOOrphans']['WarningsAndErrors']) {
if ($Script:Reporting['GPOBroken']['WarningsAndErrors']) {
New-HTMLSection -Name 'Warnings & Errors to Review' {
New-HTMLTable -DataTable $Script:Reporting['GPOOrphans']['WarningsAndErrors'] -Filtering {
New-HTMLTable -DataTable $Script:Reporting['GPOBroken']['WarningsAndErrors'] -Filtering {
New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
}
+3 -1
View File
@@ -1,9 +1,11 @@
$Script:GPOConfiguration = [ordered] @{
GPOOrphans = $GPOZaurrOrphans
GPOBroken = $GPOZaurrOrphans
GPOBrokenLink = $GPOZaurrBrokenLink
GPOOwners = $GPOZaurrOwners
GPOConsistency = $GPOZaurrConsistency
GPODuplicates = $GPOZaurrDuplicates
GPOList = $GPOZaurrList
GPOLinks = $GPOZaurrLinks
GPOPassword = $GPOZaurrPassword
GPOPermissions = $GPOZaurrPermissionsAnalysis
GPOPermissionsAdministrative = $GPOZaurrPermissionsAdministrative
+35 -57
View File
@@ -9,8 +9,7 @@
)
$Differences = @{ }
$SysvolHash = @{ }
$GPOGUIDS = $GPOs.ID.GUID
$GPOGUIDS = ConvertFrom-DistinguishedName -DistinguishedName $GPOs.DistinguishedName
$SysVolPath = "\\$($Server)\SYSVOL\$Domain\Policies"
Write-Verbose "Get-GPOZaurrBroken - Processing SYSVOL from \\$($Server)\SYSVOL\$Domain\Policies"
try {
@@ -19,10 +18,10 @@
$Sysvol = $Null
}
foreach ($_ in $SYSVOL) {
$GUID = $_.Name -replace '{' -replace '}'
$GUID = $_.Name
$SysvolHash[$GUID] = $_
}
$Files = $SYSVOL.Name -replace '{' -replace '}'
$Files = $SYSVOL.Name
if ($Files) {
$Comparing = Compare-Object -ReferenceObject $GPOGUIDS -DifferenceObject $Files -IncludeEqual
foreach ($_ in $Comparing) {
@@ -30,8 +29,10 @@
# we skip policy definitions
continue
}
$ADStatus = $PoliciesAD[$_.InputObject]
if ($_.SideIndicator -eq '==') {
$Found = 'Exists'
#$Found = 'Exists'
$Found = $ADStatus
} elseif ($_.SideIndicator -eq '<=') {
$Found = 'Not available on SYSVOL'
} elseif ($_.SideIndicator -eq '=>') {
@@ -41,8 +42,10 @@
$Found = 'Not available in AD'
}
} else {
# This shouldn't happen at all
$Found = 'Orphaned GPO'
}
$Differences[$_.InputObject] = $Found
}
}
@@ -50,85 +53,60 @@
$Count = 0
foreach ($GPO in $GPOS) {
$Count++
Write-Verbose "Get-GPOZaurrBroken - Processing [$($GPO.DomainName)]($Count/$($GPOS.Count)) $($GPO.DisplayName)"
if ($null -ne $SysvolHash[$GPO.Id.GUID].FullName) {
$FullPath = $SysvolHash[$GPO.Id.GUID].FullName
try {
$ACL = Get-Acl -Path $SysvolHash[$GPO.Id.GUID].FullName -ErrorAction Stop -Verbose:$false
$Owner = $ACL.Owner
$ErrorMessage = ''
} catch {
Write-Warning "Get-GPOZaurrBroken - ACL reading (1) failed for $FullPath with error: $($_.Exception.Message)"
$ACL = $null
$Owner = ''
$ErrorMessage = $_.Exception.Message
}
$GPOGuid = ConvertFrom-DistinguishedName -DistinguishedName $GPO.DistinguishedName
if ($GPO.DisplayName) {
$GPODisplayName = $GPO.DisplayName
$GPOName = $GPO.Name
Write-Verbose "Get-GPOZaurrBroken - Processing [$($Domain)]($Count/$($GPOS.Count)) $($GPO.DisplayName)"
} else {
$FullPath = -join ($SysVolPath, "\{$($GPO.Id.Guid)}")
$ACL = $null
$Owner = ''
$GPOName = $GPOGuid
$GPODisplayName = $GPOGuid
Write-Verbose "Get-GPOZaurrBroken - Processing [$($Domain)]($Count/$($GPOS.Count)) $($GPOGuid)"
}
if ($null -ne $SysvolHash[$GPOGuid].FullName) {
$FullPath = $SysvolHash[$GPOGuid].FullName
$ErrorMessage = ''
} else {
$FullPath = -join ($SysVolPath, "\$($GPOGuid)")
$ErrorMessage = 'Not found on SYSVOL'
}
if ($null -eq $Differences[$GPO.Id.Guid]) {
$SysVolStatus = 'Unknown Issue'
if ($null -eq $Differences[$GPOGuid]) {
$SysVolStatus = 'Unknown issue'
} else {
$SysVolStatus = $Differences[$GPO.Id.Guid]
$SysVolStatus = $Differences[$GPOGuid]
}
[PSCustomObject] @{
DisplayName = $GPO.DisplayName
DisplayName = $GPODisplayName
Status = $SysVolStatus
DomainName = $GPO.DomainName
DomainName = $Domain
SysvolServer = $Server
SysvolStatus = $SysVolStatus
GpoStatus = $GPO.GpoStatus
Owner = $GPO.Owner
FileOwner = $Owner
Id = $GPO.Id.Guid
ObjectClass = $GPO.ObjectClass
Id = $GPOName
Path = $FullPath
DistinguishedName = -join ("CN={", $GPO.Id.Guid, "},", $PoliciesSearchBase)
DistinguishedName = -join ("CN=", $GPOGuid, ",", $PoliciesSearchBase)
Description = $GPO.Description
CreationTime = $GPO.CreationTime
ModificationTime = $GPO.ModificationTime
UserVersion = $GPO.UserVersion
ComputerVersion = $GPO.ComputerVersion
WmiFilter = $GPO.WmiFilter
CreationTime = $GPO.Created
ModificationTime = $GPO.Modified
Error = $ErrorMessage
}
}
# Now we need to list thru Sysvol files and fine those that do not exists as GPO and create dummy GPO objects to show orphaned gpos
Write-Verbose "Get-GPOZaurrBroken - Processing SYSVOL differences"
foreach ($_ in $Differences.Keys) {
if ($Differences[$_] -in 'Not available in AD', 'Permissions issue') {
if ($Differences[$_] -in 'Not available in AD') {
$FullPath = $SysvolHash[$_].FullName
try {
$ACL = Get-Acl -Path $FullPath -ErrorAction Stop
$Owner = $ACL.Owner
$ErrorMessage = ''
} catch {
Write-Warning "Get-GPOZaurrBroken - ACL reading (2) failed for $FullPath with error: $($_.Exception.Message)"
$ACL = $null
$Owner = $null
$ErrorMessage = $_.Exception.Message
}
[PSCustomObject] @{
DisplayName = $SysvolHash[$_].BaseName
Status = $Differences[$_]
DomainName = $Domain
SysvolServer = $Server
SysvolStatus = 'Exists' #$Differences[$GPO.Id.Guid]
GpoStatus = $Differences[$_]
Owner = ''
FileOwner = $Owner
ObjectClass = ''
Id = $_
Path = $FullPath
DistinguishedName = -join ("CN={", $_, "},", $PoliciesSearchBase)
DistinguishedName = -join ("CN=", $_, ",", $PoliciesSearchBase)
Description = $null
CreationTime = $SysvolHash[$_].CreationTime
ModificationTime = $SysvolHash[$_].LastWriteTime
UserVersion = $null
ComputerVersion = $null
WmiFilter = $null
Error = $ErrorMessage
}
}
+5 -1
View File
@@ -47,6 +47,10 @@
if ($OwnerOnly -or $PermissionsOnly -and $Type) {
Write-Warning "Get-GPOZaurr - Using PermissionOnly or OwnerOnly with Type is not supported. "
}
if (-not $GPOPath) {
# This is needed, because Get-GPOReport doesn't deliver full scope of links, just some of it. It doesn't cover OUs with blocked inheritance, sites or crosslinked
$LinksSummaryCache = Get-GPOZaurrLink -AsHashTable -Summary -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
}
}
Process {
if (-not $GPOPath) {
@@ -86,7 +90,7 @@
Write-Warning "Get-GPOZaurr - Failed to get [$($GPO.DomainName)]($Count/$($GroupPolicies.Count)) $($GPO.DisplayName) GPOReport: $($_.Exception.Message). Skipping."
continue
}
Get-XMLGPO -OwnerOnly:$OwnerOnly.IsPresent -XMLContent $XMLContent -GPO $GPO -PermissionsOnly:$PermissionsOnly.IsPresent -ADAdministrativeGroups $ADAdministrativeGroups -ReturnObject:$ReturnObject.IsPresent -ExcludeGroupPolicies $ExcludeGPO -Type $Type
Get-XMLGPO -OwnerOnly:$OwnerOnly.IsPresent -XMLContent $XMLContent -GPO $GPO -PermissionsOnly:$PermissionsOnly.IsPresent -ADAdministrativeGroups $ADAdministrativeGroups -ReturnObject:$ReturnObject.IsPresent -ExcludeGroupPolicies $ExcludeGPO -Type $Type -LinksSummaryCache $LinksSummaryCache
} else {
$GPO
}
+58 -16
View File
@@ -1,4 +1,47 @@
function Get-GPOZaurrBroken {
<#
.SYNOPSIS
Detects broken or otherwise damaged Group Policies
.DESCRIPTION
Detects broken or otherwise damaged Group Policies providing insight whether GPO exists in both AD and SYSVOL.
It provides few statuses:
- Permissions issue - means account couldn't read GPO due to permissions
- ObjectClass issue - means that ObjectClass is of type Container, rather than expected groupPolicyContainer
- Not available on SYSVOL - means SYSVOL data is missing, yet AD metadata is available
- Not available in AD - means AD metadata is missing, yet SYSVOL data is available
- Exists - means AD metadata and SYSVOL data are available
.PARAMETER Forest
Target different Forest, by default current forest is used
.PARAMETER ExcludeDomains
Exclude domain from search, 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 IncludeDomains
Include only specific domains, by default whole forest is scanned
.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 VerifyDomainControllers
Forces cmdlet to check GPO Existance on Domain Controllers rather then per domain
.EXAMPLE
Get-GPOZaurrBroken -Verbose | Format-Table
.NOTES
General notes
#>
[alias('Get-GPOZaurrSysvol')]
[cmdletBinding()]
param(
@@ -8,7 +51,6 @@
[alias('Domain', 'Domains')][string[]] $IncludeDomains,
[alias('DomainControllers')][string[]] $IncludeDomainControllers,
[switch] $SkipRODC,
[Array] $GPOs,
[System.Collections.IDictionary] $ExtendedForestInformation,
[switch] $VerifyDomainControllers
)
@@ -21,31 +63,31 @@
$PoliciesAD = @{}
if ($SystemsContainer) {
$PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer)
$PoliciesInAD = Get-ADObject -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer
$PoliciesInAD = Get-ADObject -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer -Properties Name, gPCFileSysPath, DisplayName, DistinguishedName, Description, Created, Modified, ObjectClass, ObjectGUID
foreach ($Policy in $PoliciesInAD) {
$GUIDFromDN = ConvertFrom-DistinguishedName -DistinguishedName $Policy.DistinguishedName
$GUIDFromDN = $GUIDFromDN -replace '{' -replace '}'
$GUID = $Policy.Name -replace '{' -replace '}'
if ($GUID -and $GUIDFromDN) {
$PoliciesAD[$GUIDFromDN] = 'Exists'
if ($Policy.ObjectClass -eq 'Container') {
# This usually means GPO deletion process somehow failed and while object itself stayed it isn't groupPolicyContainer anymore
$PoliciesAD[$GUIDFromDN] = 'ObjectClass issue'
} else {
$PoliciesAD[$GUIDFromDN] = 'Permissions issue'
$GUID = $Policy.Name
if ($GUID -and $GUIDFromDN) {
$PoliciesAD[$GUIDFromDN] = 'Exists'
} else {
$PoliciesAD[$GUIDFromDN] = 'Permissions issue'
}
}
}
} else {
Write-Warning "Get-GPOZaurrBroken - Couldn't get GPOs from $Domain. Skipping"
}
Try {
[Array]$GPOs = Get-GPO -All -Domain $Domain -Server $QueryServer
} catch {
Write-Warning "Get-GPOZaurrBroken - Couldn't get GPOs from $Domain. Error: $($_.Exception.Message)"
continue
}
if ($GPOs.Count -ge 2) {
if ($PoliciesInAD.Count -ge 2) {
if (-not $VerifyDomainControllers) {
Test-SysVolFolders -GPOs $GPOs -Server $Domain -Domain $Domain -PoliciesAD $PoliciesAD -PoliciesSearchBase $PoliciesSearchBase
Test-SysVolFolders -GPOs $PoliciesInAD -Server $Domain -Domain $Domain -PoliciesAD $PoliciesAD -PoliciesSearchBase $PoliciesSearchBase
} else {
foreach ($Server in $ForestInformation['DomainDomainControllers']["$Domain"]) {
Write-Verbose "Get-GPOZaurrBroken - Processing $Domain \ $($Server.HostName.Trim())"
Test-SysVolFolders -GPOs $GPOs -Server $Server.Hostname -Domain $Domain -PoliciesAD $PoliciesAD -PoliciesSearchBase $PoliciesSearchBase
Test-SysVolFolders -GPOs $PoliciesInAD -Server $Server.Hostname -Domain $Domain -PoliciesAD $PoliciesAD -PoliciesSearchBase $PoliciesSearchBase
}
}
} else {
+43
View File
@@ -0,0 +1,43 @@
function Get-GPOZaurrBrokenLink {
[cmdletBinding()]
param(
[alias('ForestName')][string] $Forest,
[string[]] $ExcludeDomains,
[alias('Domain', 'Domains')][string[]] $IncludeDomains,
[System.Collections.IDictionary] $ExtendedForestInformation
)
$PoliciesAD = @{}
$ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation -Extended
foreach ($Domain in $ForestInformation.Domains) {
$QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
$SystemsContainer = $ForestInformation['DomainsExtended'][$Domain].SystemsContainer
if ($SystemsContainer) {
$PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer)
$PoliciesInAD = Get-ADObject -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer -Properties Name, gPCFileSysPath, DisplayName, DistinguishedName, Description, Created, Modified, ObjectClass, ObjectGUID
foreach ($Policy in $PoliciesInAD) {
$GUIDFromDN = ConvertFrom-DistinguishedName -DistinguishedName $Policy.DistinguishedName
# $Key = "$($Domain)$($GuidFromDN)"
$Key = $Policy.DistinguishedName
if ($Policy.ObjectClass -eq 'Container') {
# This usually means GPO deletion process somehow failed and while object itself stayed it isn't groupPolicyContainer anymore
$PoliciesAD[$Key] = 'ObjectClass issue'
} else {
$GUID = $Policy.Name
if ($GUID -and $GUIDFromDN) {
$PoliciesAD[$Key] = 'Exists'
} else {
$PoliciesAD[$Key] = 'Permissions issue'
}
}
}
} else {
Write-Warning "Get-GPOZaurrBroken - Couldn't get GPOs from $Domain. Skipping"
}
}
$Links = Get-GPOZaurrLinkLoop -Linked 'All' -ForestInformation $ForestInformation
foreach ($Link in $Links) {
if (-not $PoliciesAD[$Link.GPODistinguishedName]) {
$Link
}
}
}
+104 -166
View File
@@ -1,13 +1,15 @@
function Get-GPOZaurrLink {
[cmdletbinding()]
[cmdletbinding(DefaultParameterSetName = 'Linked')]
param(
[parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
# weirdly enough site doesn't really work this way unless you give it 'CN=Configuration,DC=ad,DC=evotec,DC=xyz' as SearchBase
[parameter(ParameterSetName = 'Filter')][string] $Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')",
# site doesn't really work this way unless you give it 'CN=Configuration,DC=ad,DC=evotec,DC=xyz' as SearchBase
[parameter(ParameterSetName = 'Filter')][string] $Filter, # "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')"
[parameter(ParameterSetName = 'Filter')][string] $SearchBase,
[parameter(ParameterSetName = 'Filter')][Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope,
[parameter(ParameterSetName = 'Linked', Mandatory)][validateset('Root', 'DomainControllers', 'Site', 'Other')][string] $Linked,
[parameter(ParameterSetName = 'Linked')][validateset('All', 'Root', 'DomainControllers', 'Site', 'OrganizationalUnit')][string[]] $Linked,
[parameter(ParameterSetName = 'Site')][string[]] $Site,
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
@@ -19,30 +21,47 @@
[parameter(ParameterSetName = 'Linked')]
[switch] $SkipDuplicates,
[parameter(ParameterSetName = 'Site')]
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[System.Collections.IDictionary] $GPOCache,
[parameter(ParameterSetName = 'Site')]
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[alias('ForestName')][string] $Forest,
[parameter(ParameterSetName = 'Site')]
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[string[]] $ExcludeDomains,
[parameter(ParameterSetName = 'Site')]
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[alias('Domain', 'Domains')][string[]] $IncludeDomains,
[parameter(ParameterSetName = 'Site')]
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[System.Collections.IDictionary] $ExtendedForestInformation
[System.Collections.IDictionary] $ExtendedForestInformation,
[parameter(ParameterSetName = 'Site')]
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[switch] $AsHashTable,
[parameter(ParameterSetName = 'Site')]
[parameter(ParameterSetName = 'Filter')]
[parameter(ParameterSetName = 'ADObject')]
[parameter(ParameterSetName = 'Linked')]
[switch] $Summary
)
Begin {
$CacheReturnedGPOs = [ordered] @{}
@@ -52,6 +71,7 @@
# While initially we used $ForestInformation.Domains but the thing is GPOs can be linked to other domains so we need to get them all so we can use cache of it later on even if we're processing just one domain
# That's why we use $ForestInformation.Forest.Domains instead
foreach ($Domain in $ForestInformation.Forest.Domains) {
Write-Verbose "Get-GPOZaurrLink - Building GPO cache for domain $Domain"
$QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
Get-GPO -All -DomainName $Domain -Server $QueryServer | ForEach-Object {
$GPOCache["$Domain$($_.ID.Guid)"] = $_
@@ -60,173 +80,91 @@
}
}
Process {
if (-not $ADObject) {
if ($Linked) {
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]
$getGPOLoopSplat = @{
Linked = $Linked
ForestInformation = $ForestInformation
SearchScope = $SearchScope
SearchBase = $SearchBase
ADObject = $ADObject
Filter = $Filter
SkipDuplicates = $SkipDuplicates
Site = $Site
}
Remove-EmptyValue -Hashtable $getGPOLoopSplat -Recursive
$getGPOLoopSplat['CacheReturnedGPOs'] = $CacheReturnedGPOs
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')
$LinkedRoot = $false
$LinkedRootPlaces = [System.Collections.Generic.List[string]]::new()
$LinkedSite = $false
$LinkedSitePlaces = [System.Collections.Generic.List[string]]::new()
$LinkedOU = $false
$LinkedCrossDomain = $false
$LinkedCrossDomainPlaces = [System.Collections.Generic.List[string]]::new()
foreach ($InternalLink in $Link) {
if ($InternalLink.ObjectClass -eq 'domainDNS') {
$LinkedRoot = $true
$LinkedRootPlaces.Add($InternalLink.DistinguishedName)
} elseif ($InternalLink.ObjectClass -eq 'site') {
$LinkedSite = $true
$LinkedSitePlaces.Add($InternalLink.Name)
} else {
$LinkedOU = $true
}
if ($Linked -contains 'DomainControllers') {
$SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']
#if ($SearchBase -notlike "*$DomainDistinguishedName") {
# we check if SearchBase is part of domain distinugishname. If it isn't we skip
# continue
#}
$Splat['Filter'] = "(objectClass -eq 'organizationalUnit')"
$Splat['SearchBase'] = $SearchBase
try {
$ADObjectGPO = Get-ADObject @Splat
} catch {
Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
}
foreach ($_ in $ADObjectGPO) {
$OutputGPOs = Get-PrivGPOZaurrLink -Object $_ -Limited:$Limited.IsPresent -GPOCache $GPOCache
foreach ($OutputGPO in $OutputGPOs) {
if (-not $SkipDuplicates) {
$OutputGPO
} else {
$UniqueGuid = -join ($OutputGPO.DomainName, $OutputGPO.Guid)
if (-not $CacheReturnedGPOs[$UniqueGuid]) {
$CacheReturnedGPOs[$UniqueGuid] = $OutputGPO
$OutputGPO
}
}
}
}
}
if ($Linked -contains 'Root') {
$SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
#if ($SearchBase -notlike "*$DomainDistinguishedName") {
# we check if SearchBase is part of domain distinugishname. If it isn't we skip
# continue
# }
$Splat['Filter'] = "objectClass -eq 'domainDNS'"
$Splat['SearchBase'] = $SearchBase
try {
$ADObjectGPO = Get-ADObject @Splat
} catch {
Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
}
foreach ($_ in $ADObjectGPO) {
$OutputGPOs = Get-PrivGPOZaurrLink -Object $_ -Limited:$Limited.IsPresent -GPOCache $GPOCache
foreach ($OutputGPO in $OutputGPOs) {
if (-not $SkipDuplicates) {
$OutputGPO
} else {
$UniqueGuid = -join ($OutputGPO.DomainName, $OutputGPO.Guid)
if (-not $CacheReturnedGPOs[$UniqueGuid]) {
$CacheReturnedGPOs[$UniqueGuid] = $OutputGPO
$OutputGPO
}
}
}
}
}
if ($Linked -contains 'Site') {
# 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'])
# if ($SearchBase -notlike "*$DomainDistinguishedName") {
# we check if SearchBase is part of domain distinugishname. If it isn't we skip
#continue
#}
$Splat['Filter'] = "(objectClass -eq 'site')"
$Splat['SearchBase'] = $SearchBase
try {
$ADObjectGPO = Get-ADObject @Splat
} catch {
Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
}
foreach ($_ in $ADObjectGPO) {
Get-PrivGPOZaurrLink -Object $_ -Limited:$Limited.IsPresent -GPOCache $GPOCache
}
}
}
if ($Linked -contains 'Other') {
$SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
#if ($SearchBase -notlike "*$DomainDistinguishedName") {
# we check if SearchBase is part of domain distinugishname. If it isn't we skip
# continue
#}
$Splat['Filter'] = "(objectClass -eq 'organizationalUnit')"
$Splat['SearchBase'] = $SearchBase
try {
$ADObjectGPO = Get-ADObject @Splat
} catch {
Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
}
foreach ($_ in $ADObjectGPO) {
if ($_.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) {
# other skips Domain Root
} elseif ($_.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']) {
# other skips Domain Controllers
} else {
$OutputGPOs = Get-PrivGPOZaurrLink -Object $_ -Limited:$Limited.IsPresent -GPOCache $GPOCache
foreach ($OutputGPO in $OutputGPOs) {
if (-not $SkipDuplicates) {
$OutputGPO
} else {
$UniqueGuid = -join ($OutputGPO.DomainName, $OutputGPO.Guid)
if (-not $CacheReturnedGPOs[$UniqueGuid]) {
$CacheReturnedGPOs[$UniqueGuid] = $OutputGPO
$OutputGPO
}
}
}
}
}
$CN = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $InternalLink.distinguishedName
$GPOCN = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $InternalLink.GPODomainDistinguishedName
if ($CN -ne $GPOCN) {
$LinkedCrossDomain = $true
$LinkedCrossDomainPlaces.Add($InternalLink.DistinguishedName)
}
}
} else {
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)"
}
foreach ($_ in $ADObjectGPO) {
$OutputGPOs = Get-PrivGPOZaurrLink -Object $_ -Limited:$Limited.IsPresent -GPOCache $GPOCache
foreach ($OutputGPO in $OutputGPOs) {
if (-not $SkipDuplicates) {
$OutputGPO
} else {
$UniqueGuid = -join ($OutputGPO.DomainName, $OutputGPO.Guid)
if (-not $CacheReturnedGPOs[$UniqueGuid]) {
$CacheReturnedGPOs[$UniqueGuid] = $OutputGPO
$OutputGPO
}
}
}
}
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
LinkedCrossDomain = $LinkedCrossDomain
LinkedRoot = $LinkedRoot
LinkedSite = $LinkedSite
LinkedOU = $LinkedOU
LinkedSitePlaces = $LinkedSitePlaces
LinkedRootPlaces = $LinkedRootPlaces
LinkedCrossDomainPlaces = $LinkedCrossDomainPlaces
Links = $Link.DistinguishedName
LinksObjects = $Link
}
$SummaryHashtable[$Key] = $SummaryLink
}
if ($AsHashTable -and $Summary) {
$SummaryHashtable
} elseif ($AsHashTable) {
$HashTable
} elseif ($Summary) {
$SummaryHashtable.Values
}
} else {
foreach ($Object in $ADObject) {
Get-PrivGPOZaurrLink -Object $Object -Limited:$Limited.IsPresent -GPOCache $GPOCache
}
Get-GPOZaurrLinkLoop @getGPOLoopSplat
}
}
End {
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -21,7 +21,7 @@
[parameter(ParameterSetName = 'Level', Mandatory)][int] $Limit,
# ParameterSet4
[parameter(ParameterSetName = 'Linked', Mandatory)][validateset('Root', 'DomainControllers', 'Site', 'Other')][string] $Linked,
[parameter(ParameterSetName = 'Linked', Mandatory)][validateset('Root', 'DomainControllers', 'Site', 'OrganizationalUnit')][string] $Linked,
# ParameterSet5
[parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
+58 -6
View File
@@ -1,8 +1,54 @@
function Remove-GPOZaurrBroken {
<#
.SYNOPSIS
Finds and removes broken Group Policies from SYSVOL or AD or both.
.DESCRIPTION
Finds and removes broken Group Policies from SYSVOL or AD or both. Assesment is based on Get-GPOZaurrBroken and there are 3 supported types:
- AD - meaning GPOs which have no SYSVOL content will be deleted from AD
- SYSVOL - meaning GPOs which have no AD content will be deleted from SYSVOL
- ObjectClass - meaning GPOs which have ObjectClass category of Container rather than groupPolicyContainer will be deleted from AD & SYSVOL
.PARAMETER Type
Choose one or more types to delete. Options are AD, ObjectClass, SYSVOL
.PARAMETER BackupPath
Path to optional backup of SYSVOL content before deletion
.PARAMETER BackupDated
Forces backup to be created within folder that has date in it
.PARAMETER LimitProcessing
Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed
.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
Remove-GPOZaurrBroken -Verbose -WhatIf -Type AD, SYSVOL
.EXAMPLE
Remove-GPOZaurrBroken -Verbose -WhatIf -Type AD, SYSVOL -IncludeDomains 'ad.evotec.pl' -LimitProcessing 2
.EXAMPLE
Remove-GPOZaurrBroken -Verbose -IncludeDomains 'ad.evotec.xyz' -BackupPath $Env:UserProfile\Desktop\MyBackup1 -WhatIf -Type AD, SYSVOL
.NOTES
General notes
#>
[alias('Remove-GPOZaurrOrphaned')]
[cmdletBinding(SupportsShouldProcess)]
param(
[ValidateSet('SYSVOL', 'AD')][string[]] $Type = @('SYSVOL', 'AD'),
[parameter(Mandatory, Position = 0)][ValidateSet('SYSVOL', 'AD', 'ObjectClass')][string[]] $Type,
[string] $BackupPath,
[switch] $BackupDated,
[int] $LimitProcessing = [int32]::MaxValue,
@@ -32,10 +78,15 @@
$_
}
}
if ($Type -contains 'ObjectClass') {
if ($_.Status -eq 'ObjectClass issue') {
$_
}
}
} | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object {
$GPO = $_
if ($GPO.Status -eq 'Not available in AD') {
Write-Verbose "Remove-GPOZaurrBroken - Processing [AD] $($GPO.Path)"
if ($GPO.Status -in 'Not available in AD', 'ObjectClass issue') {
Write-Verbose "Remove-GPOZaurrBroken - Removing from SYSVOL [$($GPO.Status)] $($GPO.Path)"
if ($BackupFinalPath) {
Try {
Write-Verbose "Remove-GPOZaurrBroken - Backing up $($GPO.Path)"
@@ -47,15 +98,16 @@
}
}
if ($BackupWorked -or $BackupFinalPath -eq '') {
Write-Verbose "Remove-GPOZaurrBroken - Deleting $($GPO.Path)"
Write-Verbose "Remove-GPOZaurrBroken - Removing $($GPO.Path)"
try {
Remove-Item -Recurse -Force -LiteralPath $GPO.Path -ErrorAction Stop
} catch {
Write-Warning "Remove-GPOZaurrBroken - Failed to remove file $($GPO.Path): $($_.Exception.Message)."
}
}
} elseif ($GPO.Status -eq 'Not available on SYSVOL') {
Write-Verbose "Remove-GPOZaurrBroken - Processing [SYSVOL] $($GPO.DistinguishedName)"
}
if ($GPO.Status -in 'Not available on SYSVOL', 'ObjectClass issue') {
Write-Verbose "Remove-GPOZaurrBroken - Removing from AD [$($GPO.Status)] $($GPO.DistinguishedName)"
try {
$ExistingObject = Get-ADObject -Identity $GPO.DistinguishedName -Server $GPO.DomainName -ErrorAction Stop
} catch {
+55
View File
@@ -0,0 +1,55 @@
function Repair-GPOZaurrBrokenLink {
[cmdletBinding(SupportsShouldProcess)]
param(
[alias('ForestName')][string] $Forest,
[string[]] $ExcludeDomains,
[alias('Domain', 'Domains')][string[]] $IncludeDomains,
[System.Collections.IDictionary] $ExtendedForestInformation,
[int] $LimitProcessing
)
$ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended
$Links = Get-GPOZaurrBrokenLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation
$Cache = @{}
foreach ($Link in $Links) {
if (-not $Cache[$Link.DistinguishedName]) {
$Cache[$Link.DistinguishedName] = [System.Collections.Generic.List[PSCustomObject]]::new()
}
$Cache[$Link.DistinguishedName].Add($Link)
}
$Count = 0
foreach ($Key in $Cache.Keys) {
$Count++
Write-Verbose "Repair-GPOZaurrBrokenLink - processing [$Count/$($Cache.Keys.Count)] $Key "
$Domain = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Key
$Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
$Object = Get-ADObject -Identity $Key -Server $Server -Properties gpLink
#$MatchLinks = [Regex]::Matches($Object.gpLink, '(?<=\[)(.*?)(?=\])').Value
$MatchLinks = [Regex]::Matches($Object.gpLink, '(?<=LDAP:\/\/)(.*?)(?=])').Value
$Found = $false
$FixedLinks = foreach ($Match in $MatchLinks) {
$SplittedValue = $Match -split ';'
$GPODN = $SplittedValue[0]
# Check if list of non-existing GPOs contains legitimate GPO
if ($Cache[$Key].GPODistinguishedName -notcontains $GPODN) {
"[LDAP://$Match]"
Write-Verbose "Repair-GPOZaurrBrokenLink - legitimate link to GPO $GPODN ($Key)"
} else {
$Found = $true
Write-Verbose "Repair-GPOZaurrBrokenLink - preparing for removal link to $GPODN ($Key)"
}
}
#
if ($Found) {
$NewGpLink = $($FixedLinks -join '')
try {
Write-Verbose "Repair-GPOZaurrBrokenLink - setting gpLink to $Key - $NewGPLink"
Set-ADObject -Identity $Key -Server $Server -Replace @{ gPLink = $NewGpLink } -ErrorAction Stop
} catch {
Write-Warning "Repair-GPOZaurrBrokenLink - couldn't replace gpLink at $Key with $NewGpLink"
}
if ($LimitProcessing -eq $Count) {
break
}
}
}
}
+32
View File
@@ -57,6 +57,38 @@ That's it. Whenever there's a new version, you run the command, and you can enjo
## Changelog
- 0.0.103 - 2021.01.04
- [x] Improved `Get-GPOZaurr`
- [x] Added new report `GPOBrokenLink`
- [x] Added `Get-GPOZaurrBrokenLink`
- [x] Added `Repair-GPOZaurrBrokenLink`
- 0.0.102 - 2021.01.02
- [x] Improved `Get-GPOZaurrLink`
- [x] Supports all links across forest
- [x] Renamed Linked validate set from `Other` to `OrganizationalUnit`
- [x] Improved `Get-GPOZaurrLinkSummary`
- [x] Improved/BugFix `Get-GPOZaurr` to properly detect linked GPOs in sites/cross-domain
- [x] Improved `Invoke-GPOZaurrPermission`
- [x] Renamed Linked validate set from `Other` to `OrganizationalUnit`
- [x] Improved `Invoke-GPOZaurr`
- [x] Added `GPOLinks` basic list
- 0.0.101 - 23.12.2020
- [x] Improved `Get-GPOZaurrBroken`
- [x] It now detects `ObjectClass Issue`
- [x] Heavily improved performance
- [x] Removed some useless properties for this particular cmdlet
- [x] All states: `Not available on SYSVOL`, `Not available in AD`, `Exists`, `Permissions Issue`, `ObjectClass Issue`
- [x] Improved help
- [x] Improved `Remove-GPOZaurrBroken`
- [x] It now deals with `ObjectClass Issue`
- [x] Heavily improved performance
- [x] Removed some useless properties for this particular cmdlet
- [x] Now requires manual type insert AD, SYSVOL or ObjectClass (or all of them). Before it was auto using AD/SYSVOL.
- [x] Improved help
- [x] Improved `Invoke-GPOZaurr`
- [x] Type `GPOList`
- [x] Renamed `GPOOrphans` to `GPOBroken`
- [x] Improved `GPOBroken` with `ObjectClass issue`
- 0.0.100 - 21.12.2020
- [x] Improved `Invoke-GPOZaurr`
- [x] Type `GPOPermissionsRead`