diff --git a/GPOZaurr.psd1 b/GPOZaurr.psd1 index 4a1e25a..2a4bae7 100644 --- a/GPOZaurr.psd1 +++ b/GPOZaurr.psd1 @@ -6,7 +6,7 @@ CompatiblePSEditions = @('Desktop') Copyright = '(c) 2011 - 2020 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-GPOZaurrDictionary', 'Get-GPOZaurrFiles', 'Get-GPOZaurrFilesPolicyDefinition', 'Get-GPOZaurrFolders', 'Get-GPOZaurrInheritance', 'Get-GPOZaurrLegacyFiles', 'Get-GPOZaurrLink', 'Get-GPOZaurrLinkSummary', 'Get-GPOZaurrOwner', 'Get-GPOZaurrPassword', 'Get-GPOZaurrPermission', 'Get-GPOZaurrPermissionConsistency', 'Get-GPOZaurrPermissionRoot', 'Get-GPOZaurrPermissionSummary', 'Get-GPOZaurrSysvol', 'Get-GPOZaurrSysvolDFSR', 'Get-GPOZaurrWMI', 'Invoke-GPOZaurr', 'Invoke-GPOZaurrPermission', 'Invoke-GPOZaurrSupport', 'New-GPOZaurrWMI', 'Remove-GPOPermission', 'Remove-GPOZaurr', 'Remove-GPOZaurrFolders', 'Remove-GPOZaurrLegacyFiles', 'Remove-GPOZaurrOrphanedSysvolFolders', 'Remove-GPOZaurrPermission', 'Remove-GPOZaurrWMI', 'Repair-GPOZaurrPermissionConsistency', 'Restore-GPOZaurr', 'Save-GPOZaurrFiles', 'Set-GPOOwner', 'Set-GPOZaurrOwner') + FunctionsToExport = @('Add-GPOPermission', 'Add-GPOZaurrPermission', 'Backup-GPOZaurr', 'Clear-GPOZaurrSysvolDFSR', 'ConvertFrom-CSExtension', 'Find-CSExtension', 'Get-GPOZaurr', 'Get-GPOZaurrAD', 'Get-GPOZaurrBackupInformation', 'Get-GPOZaurrDictionary', 'Get-GPOZaurrFiles', 'Get-GPOZaurrFilesPolicyDefinition', 'Get-GPOZaurrFolders', 'Get-GPOZaurrInheritance', 'Get-GPOZaurrLegacyFiles', 'Get-GPOZaurrLink', 'Get-GPOZaurrLinkSummary', 'Get-GPOZaurrOwner', 'Get-GPOZaurrPassword', 'Get-GPOZaurrPermission', 'Get-GPOZaurrPermissionConsistency', 'Get-GPOZaurrPermissionRoot', 'Get-GPOZaurrPermissionSummary', 'Get-GPOZaurrSysvol', 'Get-GPOZaurrSysvolDFSR', 'Get-GPOZaurrWMI', 'Invoke-GPOZaurr', 'Invoke-GPOZaurrPermission', 'Invoke-GPOZaurrSupport', 'New-GPOZaurrWMI', 'Remove-GPOPermission', 'Remove-GPOZaurr', 'Remove-GPOZaurrFolders', 'Remove-GPOZaurrLegacyFiles', 'Remove-GPOZaurrOrphaned', 'Remove-GPOZaurrPermission', 'Remove-GPOZaurrWMI', 'Repair-GPOZaurrPermissionConsistency', 'Restore-GPOZaurr', 'Save-GPOZaurrFiles', 'Set-GPOOwner', 'Set-GPOZaurrOwner') GUID = 'f7d4c9e4-0298-4f51-ad77-e8e3febebbde' ModuleVersion = '0.0.62' PowerShellVersion = '5.1' @@ -22,11 +22,11 @@ ModuleName = 'PSSharedGoods' Guid = 'ee272aa8-baaa-4edf-9f45-b6d6f7d844fe' }, @{ - ModuleVersion = '0.0.92' + ModuleVersion = '0.0.94' ModuleName = 'ADEssentials' Guid = '9fc9fd61-7f11-4f4b-a527-084086f1905f' }, @{ - ModuleVersion = '0.0.104' + ModuleVersion = '0.0.109' ModuleName = 'PSWriteHTML' Guid = 'a7bdf640-f5cb-4acf-9de0-365b322d245c' }, 'ActiveDirectory', 'GroupPolicy', 'CimCmdlets', 'Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Utility') diff --git a/Private/Test-SysvolFolders.ps1 b/Private/Test-SysvolFolders.ps1 index 2908c69..4c26ea2 100644 --- a/Private/Test-SysvolFolders.ps1 +++ b/Private/Test-SysvolFolders.ps1 @@ -3,7 +3,9 @@ param( [Array] $GPOs, [string] $Server, - [string] $Domain + [string] $Domain, + [System.Collections.IDictionary] $PoliciesAD, + $PoliciesSearchBase ) $Differences = @{ } $SysvolHash = @{ } @@ -22,12 +24,20 @@ if ($Files) { $Comparing = Compare-Object -ReferenceObject $GPOGUIDS -DifferenceObject $Files -IncludeEqual foreach ($_ in $Comparing) { + if ($_.InputObject -eq 'PolicyDefinitions') { + # we skip policy definitions + continue + } if ($_.SideIndicator -eq '==') { $Found = 'Exists' } elseif ($_.SideIndicator -eq '<=') { $Found = 'Not available on SYSVOL' } elseif ($_.SideIndicator -eq '=>') { - $Found = 'Orphaned GPO' + if ($PoliciesAD[$_.InputObject]) { + $Found = $PoliciesAD[$_.InputObject] + } else { + $Found = 'Not available in AD' + } } else { $Found = 'Orphaned GPO' } @@ -52,33 +62,34 @@ $ACL = $null } if ($null -eq $Differences[$GPO.Id.Guid]) { - $SysVolStatus = 'Not available on SYSVOL' + $SysVolStatus = 'Unknown Issue' } else { $SysVolStatus = $Differences[$GPO.Id.Guid] } [PSCustomObject] @{ - DisplayName = $GPO.DisplayName - Status = $Differences[$GPO.Id.Guid] - DomainName = $GPO.DomainName - SysvolServer = $Server - SysvolStatus = $SysVolStatus - Owner = $GPO.Owner - FileOwner = $Owner - Id = $GPO.Id.Guid - GpoStatus = $GPO.GpoStatus - Path = $FullPath - Description = $GPO.Description - CreationTime = $GPO.CreationTime - ModificationTime = $GPO.ModificationTime - UserVersion = $GPO.UserVersion - ComputerVersion = $GPO.ComputerVersion - WmiFilter = $GPO.WmiFilter - Error = $ErrorMessage + DisplayName = $GPO.DisplayName + Status = $SysVolStatus + DomainName = $GPO.DomainName + SysvolServer = $Server + SysvolStatus = $SysVolStatus + GpoStatus = $GPO.GpoStatus + Owner = $GPO.Owner + FileOwner = $Owner + Id = $GPO.Id.Guid + Path = $FullPath + DistinguishedName = -join ("CN={", $GPO.Id.Guid, "},", $PoliciesSearchBase) + Description = $GPO.Description + CreationTime = $GPO.CreationTime + ModificationTime = $GPO.ModificationTime + UserVersion = $GPO.UserVersion + ComputerVersion = $GPO.ComputerVersion + WmiFilter = $GPO.WmiFilter + 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 foreach ($_ in $Differences.Keys) { - if ($Differences[$_] -eq 'Orphaned GPO') { + if ($Differences[$_] -in 'Not available in AD', 'Permissions issue') { if ($SysvolHash[$_].BaseName -notcontains 'PolicyDefinitions') { $FullPath = $SysvolHash[$_].FullName try { @@ -94,15 +105,16 @@ [PSCustomObject] @{ DisplayName = $SysvolHash[$_].BaseName - Status = 'Orphaned GPO' + Status = $Differences[$_] DomainName = $Domain SysvolServer = $Server - SysvolStatus = $Differences[$GPO.Id.Guid] + SysvolStatus = 'Exists' #$Differences[$GPO.Id.Guid] + GpoStatus = $Differences[$_] Owner = '' FileOwner = $Owner Id = $_ - GpoStatus = 'Orphaned' Path = $FullPath + DistinguishedName = -join ("CN={", $_, "},", $PoliciesSearchBase) Description = $null CreationTime = $SysvolHash[$_].CreationTime ModificationTime = $SysvolHash[$_].LastWriteTime diff --git a/Public/Get-GPOZaurrSysvol.ps1 b/Public/Get-GPOZaurrSysvol.ps1 index 19053ee..fe830dd 100644 --- a/Public/Get-GPOZaurrSysvol.ps1 +++ b/Public/Get-GPOZaurrSysvol.ps1 @@ -11,10 +11,26 @@ [System.Collections.IDictionary] $ExtendedForestInformation, [switch] $VerifyDomainControllers ) - $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation + $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation -Extended foreach ($Domain in $ForestInformation.Domains) { Write-Verbose "Get-WinADGPOSysvolFolders - Processing $Domain" $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0] + $SystemsContainer = $ForestInformation['DomainsExtended'][$Domain].SystemsContainer + $PoliciesAD = @{} + if ($SystemsContainer) { + $PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer) + $PoliciesInAD = Get-ADObject -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer + 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' + } else { + $PoliciesAD[$GUIDFromDN] = 'Permissions issue' + } + } + } Try { [Array]$GPOs = Get-GPO -All -Domain $Domain -Server $QueryServer } catch { @@ -23,11 +39,11 @@ } if ($GPOs.Count -ge 2) { if (-not $VerifyDomainControllers) { - Test-SysVolFolders -GPOs $GPOs -Server $Domain -Domain $Domain + Test-SysVolFolders -GPOs $GPOs -Server $Domain -Domain $Domain -PoliciesAD $PoliciesAD -PoliciesSearchBase $PoliciesSearchBase } else { foreach ($Server in $ForestInformation['DomainDomainControllers']["$Domain"]) { Write-Verbose "Get-GPOZaurrSysvol - Processing $Domain \ $($Server.HostName.Trim())" - Test-SysVolFolders -GPOs $GPOs -Server $Server.Hostname -Domain $Domain + Test-SysVolFolders -GPOs $GPOs -Server $Server.Hostname -Domain $Domain -PoliciesAD $PoliciesAD -PoliciesSearchBase $PoliciesSearchBase } } } else { diff --git a/Public/Remove-GPOZaurrOrphaned.ps1 b/Public/Remove-GPOZaurrOrphaned.ps1 new file mode 100644 index 0000000..bdae599 --- /dev/null +++ b/Public/Remove-GPOZaurrOrphaned.ps1 @@ -0,0 +1,74 @@ +function Remove-GPOZaurrOrphaned { + [cmdletBinding(SupportsShouldProcess)] + param( + [ValidateSet('SYSVOL', 'AD')][string[]] $Type = @('SYSVOL', 'AD'), + [string] $BackupPath, + [switch] $BackupDated, + [int] $LimitProcessing = [int32]::MaxValue, + + [alias('ForestName')][string] $Forest, + [string[]] $ExcludeDomains, + [alias('Domain', 'Domains')][string[]] $IncludeDomains, + [System.Collections.IDictionary] $ExtendedForestInformation + ) + if ($BackupPath) { + if ($BackupDated) { + $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))" + } else { + $BackupFinalPath = $BackupPath + } + } else { + $BackupFinalPath = '' + } + Get-GPOZaurrSysvol -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation | Where-Object { + if ($Type -contains 'SYSVOL') { + if ($_.Status -eq 'Not available in AD') { + $_ + } + } + if ($Type -contains 'AD') { + if ($_.Status -eq 'Not available on SYSVOL') { + $_ + } + } + } | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object { + if ($_.Status -eq 'Not available in AD') { + Write-Verbose "Remove-GPOZaurrOrphaned - Processing $($_.Path)" + if ($BackupFinalPath) { + Try { + Write-Verbose "Remove-GPOZaurrOrphaned - Backing up $($_.Path)" + Copy-Item -LiteralPath $_.Path -Recurse -Destination $BackupFinalPath -ErrorAction Stop + $BackupWorked = $true + } catch { + Write-Warning "Remove-GPOZaurrOrphaned - Error backing up error: $($_.Exception.Message)" + $BackupWorked = $false + } + } + if ($BackupWorked -or $BackupFinalPath -eq '') { + Write-Verbose "Remove-GPOZaurrOrphaned - Deleting $($_.Path)" + try { + Remove-Item -Recurse -Force -LiteralPath $_.Path + } catch { + Write-Warning "Remove-GPOZaurrOrphaned - Failed to remove file $($_.Path): $($_.Exception.Message)." + } + } + } elseif ($_.Status -eq 'Not available on SYSVOL') { + try { + $ExistingObject = Get-ADObject -Identity $_.DistinguishedName -Server $_.DomainName -ErrorAction Stop + } catch { + Write-Warning "Remove-GPOZaurrOrphaned - Error getting $($_.DistinguishedName) from AD error: $($_.Exception.Message)" + $ExistingObject = $null + } + if ($ExistingObject -and $ExistingObject.ObjectClass -eq 'groupPolicyContainer') { + Write-Verbose "Remove-GPOZaurrOrphaned - Removing DN: $($_.DistinguishedName) / ObjectClass: $($ExistingObject.ObjectClass)" + try { + Remove-ADObject -Server $_.DomainName -Identity $_.DistinguishedName -Recursive -Confirm:$false + } catch { + Write-Warning "Remove-GPOZaurrOrphaned - Failed to remove $($_.DistinguishedName) from AD error: $($_.Exception.Message)" + } + } else { + Write-Warning "Remove-GPOZaurrOrphaned - DistinguishedName $($_.DistinguishedName) not found or ObjectClass is not groupPolicyContainer ($($ExistingObject.ObjectClass))" + } + } + } +} \ No newline at end of file diff --git a/Public/Remove-GPOZaurrOrphanedSysvolFolders.ps1 b/Public/Remove-GPOZaurrOrphanedSysvolFolders.ps1 deleted file mode 100644 index e367b9c..0000000 --- a/Public/Remove-GPOZaurrOrphanedSysvolFolders.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -function Remove-GPOZaurrOrphanedSysvolFolders { - [cmdletBinding(SupportsShouldProcess)] - param( - [string] $BackupPath, - [switch] $BackupDated, - [int] $LimitProcessing = [int32]::MaxValue, - - [alias('ForestName')][string] $Forest, - [string[]] $ExcludeDomains, - [alias('Domain', 'Domains')][string[]] $IncludeDomains, - [System.Collections.IDictionary] $ExtendedForestInformation - ) - if ($BackupPath) { - if ($BackupDated) { - $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))" - } else { - $BackupFinalPath = $BackupPath - } - } else { - $BackupFinalPath = '' - } - Get-GPOZaurrSysvol -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation | Where-Object { - if ($_.Status -eq 'Orphaned GPO') { - $_ - } - } | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object { - Write-Verbose "Remove-GPOZaurrOrphanedSysvolFolders - Processing $($_.Path)" - if ($BackupFinalPath) { - Try { - Write-Verbose "Remove-GPOZaurrOrphanedSysvolFolders - Backing up $($_.Path)" - Copy-Item -LiteralPath $_.Path -Recurse -Destination $BackupFinalPath -ErrorAction Stop - $BackupWorked = $true - } catch { - Write-Warning "Remove-GPOZaurrOrphanedSysvolFolders - Error backing up error: $($_.Exception.Message)" - $BackupWorked = $false - } - } - if ($BackupWorked -or $BackupFinalPath -eq '') { - Write-Verbose "Remove-GPOZaurrOrphanedSysvolFolders - Deleting $($_.Path)" - try { - Remove-Item -Recurse -Force -LiteralPath $_.Path - } catch { - $ErrorMessage = $_.Exception.Message - Write-Warning "Remove-GPOZaurrOrphanedSysvolFolders - Failed to remove file $($_.Path): $($ErrorMessage)." - } - } - } -} \ No newline at end of file diff --git a/README.md b/README.md index 18966a2..bf14820 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,10 @@ That's it. Whenever there's a new version, you run the command, and you can enjo - 0.0.62 - Unreleased - Improvements to `Get-GPOZaurrPermissionConsistency` for GPOs without SYSVOL to be reported properly - Added `Get-GPOZaurrPermissionRoot` + - Renamed `Remove-GPOZaurrOrphanedSysvolFolders` to `Remove-GPOZaurrOrphaned` + - Improved `Remove-GPOZaurrOrphaned` to deal with orphaned folders but also orphaned AD GPO (No sysvol data) + - Improved `Get-GPOZaurrSysVol` to detect orphaned SYSVOL or AD GPO objects + - Improved `Get-GPOZaurrSysVol` to detect permissions issue when reading AD GPO objects - 0.0.61 - 31.08.2020 - Improvement to `Get-GPOZaurrPermissionSummary` - Fixes to `ConvertFrom-CSExtension`