mirror of
https://github.com/EvotecIT/GPOZaurr.git
synced 2026-07-26 11:49:17 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a24b1ff958 | |||
| de076a1cb4 | |||
| 663eecc01b | |||
| d1b7d71ad9 | |||
| d888f135e0 | |||
| 81ecf580c4 | |||
| 34c9d59150 | |||
| 80580b7dce |
+2
-2
@@ -6,9 +6,9 @@
|
||||
CompatiblePSEditions = @('Desktop')
|
||||
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.102'
|
||||
ModuleVersion = '0.0.103'
|
||||
PowerShellVersion = '5.1'
|
||||
PrivateData = @{
|
||||
PSData = @{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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. "
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
$Script:GPOConfiguration = [ordered] @{
|
||||
GPOBroken = $GPOZaurrOrphans
|
||||
GPOBrokenLink = $GPOZaurrBrokenLink
|
||||
GPOOwners = $GPOZaurrOwners
|
||||
GPOConsistency = $GPOZaurrConsistency
|
||||
GPODuplicates = $GPOZaurrDuplicates
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,11 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user