Improve Get-GPOZaurrBroken

This commit is contained in:
Przemyslaw Klys
2020-12-23 09:32:07 +01:00
parent a36a470538
commit 15538e8559
2 changed files with 93 additions and 73 deletions
+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
}
}
+54 -12
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,11 +63,14 @@
$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 ($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 {
$GUID = $Policy.Name
if ($GUID -and $GUIDFromDN) {
$PoliciesAD[$GUIDFromDN] = 'Exists'
} else {
@@ -33,19 +78,16 @@
}
}
}
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
} else {
Write-Warning "Get-GPOZaurrBroken - Couldn't get GPOs from $Domain. Skipping"
}
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 {