I have started to create a small PowerShell script to help detect invalid or orphaned group policies. The first version of the script searches for group policies that are not connected to an OU, have no security filtering or have all GPO settings disabled. I will add more functions to the script in the future and publish it as a small tool. But for now here is a foretaste.
The script outputs the status "Disabled" if all settings of the policy have been disabled, here is an example:
The status "Not Linked" is displayed if the group policy is not linked to any organizational unit (OU), as can be seen here:
If the security filtering is empty, the status "No Permissions" is displayed:
The script requires the PowerShell module "GroupPolicy", which is part of Windows Server 2008 R2 and higher. I have tested the script so far on Windows Server 2012 R2, Windows Server 2016 and remotely via Windows 10.
This is what the output of the script looks like in PowerShell:
And here is the actual script, which you are welcome to adapt or rewrite:
$AllGPOs = Get-GPO -All $GPOCount = $AllGPOs.count write-host "" write-host "Found $GPOCount GPOs..." write-host "Analyzing, please wait..." $InvalidGPOs = @() #Disabled GPOs $DisabledGPOs = $AllGPOs | where {$_.GpoStatus -match "AllSettingsDisabled"} foreach ($DisabledGPO in $DisabledGPOs) { $GPOName = $DisabledGPO.Displayname $InvalidGPOs += new-object PSObject -property @{GPOName="$GPOName";State="Disabled"} #write-host "Not active: $GPOName" } #GPO Links / Permissions / Empty GPOs $EnabledGPOs = $AllGPOs | where {$_.GpoStatus -notmatch "AllSettingsDisabled"} foreach ($EnabledGPO in $EnabledGPOs) { $GPOName = $EnabledGPO.Displayname [XML]$GPOReport = Get-GPOReport $GPOName -ReportType XML $GPOLinks = $GPOReport.GPO.LinksTo $GPOApplyPermission = Get-GPPermission $GPOName -All | where {$_.Permission -match "GpoApply"} if ($GPOLinks) { $GPOLinkCount = $GPOLinks.Count $DisabledGPOLinksCount = ($GPOLinks | where {$_.enabled -eq "false"}).Count if ($GPOLinkCount -eq $DisabledGPOLinksCount) { #write-host "All Links disabled: $GPOName" $InvalidGPOs += new-object PSObject -property @{GPOName="$GPOName";State="All Links disabled"} } } if (!$GPOLinks) { $Sitelinked = Get-ADObject -LDAPFilter '(objectClass=site)' -SearchBase "CN=Sites,$((Get-ADRootDSE).configurationNamingContext)" -SearchScope OneLevel -Properties gPLink | Where-Object { $_.gpLink -match $EnabledGPO.Id} if (!$Sitelinked) { #write-host "Not linked: $GPOName" $InvalidGPOs += new-object -TypeName PSObject -Property @{GPOName="$GPOName";State='Not Linked'} } } if (!$GPOApplyPermission) { #write-host "No permissions: $GPOName" $InvalidGPOs += new-object PSObject -property @{GPOName="$GPOName";State="No Permissions"} } if (!$GPOReport.GPO.Computer.ExtensionData -and !$GPOReport.GPO.User.ExtensionData) { #write-host "Empty GPO: $GPOName" $InvalidGPOs += new-object PSObject -property @{GPOName="$GPOName";State="Empty"} } } $InvalidGPOs | sort state | ft GPOName,State $InvalidGPOCount = $InvalidGPOs.Count write-host "Found $InvalidGPOCount invalid GPOs" write-host "" #Optional: delete invalid GPOs foreach ($InvalidGPO in $InvalidGPOs) { Remove-GPO $InvalidGPO.GPOName }
The lower part after #Optional can be used to delete the invalid GPOs found. The object "$InvalidGPOs" contains all invalid GPOs found and can therefore also be reused.
Maybe it will be useful to someone.
Update 15.06.2017: The updated version of the script now also lists empty group policies (without settings) and GPOs where all links to OUs are deactivated.
Update 01.07.2017: The updated version of the script now also takes into account GPOs that have been linked to AD locations. Thanks to Torsten for the hint.