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.
Hallo Frank,
danke für dein Script. Funktioniert einwandfrei.
Da ich der absolute Laie bin, würde ich gerne noch wissen wie man das Ergebnis in eine .txt Datei übertragen kann?
Beste Grüße
dogved
Ausgabe angepasst
Zeile 53
von : $InvalidGPOs | sort state | ft GPOName,State
auf $InvalidGPOs | sort state | ft GPOName,State -AutoSize
gruß Georg
Moin, lange her, dass Du Dir diese arbeit hier gemacht hast. Trotzdem: hat mir gerade sehr geholfen! Danke dafür!
Michael
Hallo Frank,
zunächste vielen Dank für die Mühe mit dem Script und natürlich auch deiner Webseite.
Mir ist allerdings ein Unstimmgkeit aufgefallen, wenn es um deaktivierte GPOs geht:
Scheinbar ist die Abfrage bei deaktivierten GPOs etwas „ungenau“, da ich interessanterweise eine GPO angezeigt bekomme, welche mit „All Links disabled“ gekennzeichnet wurde. Schau ich mir die GPO dann über GUI bzw. über ein einfaches: get-gpo -name NameDerGPO an, so wird mir angezeigt das nur die User Settings disabled wurden, nicht aber die gesamte GPO.
Bei allen anderen GPOs wo auch entweder User oder Computer disabled wurde, scheint es keine Ungenauigkeit zu geben und diese werden auch nicht gelistet.
Vielleicht fällt dir dazu ja noch etwas ein, bzw. wenn ich dir weitere Infos liefern soll, einfach raus damit.
Danke und Gruß,
Eric
Hallo Eric,
danke für den Hinweis. Ich schaue es mir an.
Gruß, Frank
Hallo Frank,
erstmal vielen Dank für das nützliche Skript :-)
Bei genauerer Betrachtung ist mir jedoch noch eine unschöne Sache aufgefallen.
Falls ein GPO auf eine AD-Site verknüpft wurde, wird dies im GPOReport nicht angezeigt und somit vom Skript als nicht verknüpft gewertet.
Beim Aktivieren des optionalen Löschens kann dies damit zu unschönen Effekten führen!!!
Da der GPOReport die Verlinkung zum Standort nicht anzeigt, kannst Du dies durch eine zusätzliche kleine LDAP Abfrage abfangen:
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-Output -inputObject „Not linked: $GPOName“
$InvalidGPOs += new-object -TypeName PSObject -Property @{GPOName=“$GPOName“;State=’Not Linked‘}
}
}
Happy scripting,
Torsten
Sehr schönes Skript!
Sofern es noch weiterentwickelt wird, wäre es hilfreich, wenn das Skript auch verknüpfte GPOs erkennt, die Verknüpfungen allerdings deaktiviert sind. Das scheint aktuell noch nicht der Fall zu sein.
Hi Sven,
danke für den Hinweis. Ich habe schon eine neue Version die ich morgen noch einmal kurz testen werde. Das neue Script erkennt dann auch leere GPOs und, wie von dir vorgeschlagen, GPOs bei denen alle Verknüpfungen deaktiviert sind.
Gruß, Frank
Hi Sven,
die aktualisierte Version findet nun auch leere GPOs und zusätzlich auch GPOs bei denen alle Verknüpfungen deaktiviert sind.
Gruß,
Frank
:-)
Vorm schreiben Website aktualsieren…..
Hallo,
ich musste bei mir
Get-GPPermission
ersetzen mit
Get-GPPermissions
Windows 2008R2
Hi Jörg,
ab Server 2012 R2 (zumindest gerade ausprobiert), ist Get-GPPermissions nur ein Alias für Get-GPPermissions:
DisplayName : Get-GPPermissions
CommandType : Alias
Definition : Get-GPPermission
Mag sein, dass es bei Server 2008 R2 noch anders ist.
Gruß, Frank
Hallo Frank,
Danke für das Powershell-Skript. Was mich schon lange nervt, sind das ich die alten klassischen administrativen Vorlagen (ADM) löschen möchte. In der Gruppenrichtlinienverwaltung, habe ich GPO von Word/Excel/Powerpoint/Outlook 2000, 2003, 2007, 2010 etc.
Gibt es eine Möglichkeit das man die alten ADM-Vorlagen (inkl. allenfalls vorhandenen Einstellungen) löschen kann? Das würde die Übersicht massiv verbessern und allenfalls auch fehlerhafte Einstellungen bereinigen…
Ich bekomme auf einem Server 2012R2 beim ausführen folgenden Fehler:
In C:\Search-InvalidGPOs.ps1:15 Zeichen:69
+ $InvalidGPOs += new-object PSObject -property @{GPOName=“$GPOName“State=“Disab …
+ ~~~~~~~~~~~~
Unerwartetes Token „State=“Disabled““ in Ausdruck oder Anweisung.
In C:\Search-InvalidGPOs.ps1:15 Zeichen:69
+ $InvalidGPOs += new-object PSObject -property @{GPOName=“$GPOName“State=“Disab …
+ ~
Das Hashliteral war unvollständig.
In C:\Search-InvalidGPOs.ps1:17 Zeichen:2
+ }
+ ~
Unerwartetes Token „}“ in Ausdruck oder Anweisung.
In C:\Search-InvalidGPOs.ps1:31 Zeichen:71
+ $InvalidGPOs += new-object PSObject -property @{GPOName=“$GPOName“State=“Not …
+ ~~~~~~~~~~
Unerwartetes Token „State=“Not Linked““ in Ausdruck oder Anweisung.
In C:\Search-InvalidGPOs.ps1:31 Zeichen:71
+ $InvalidGPOs += new-object PSObject -property @{GPOName=“$GPOName“State=“Not …
+ ~
Das Hashliteral war unvollständig.
In C:\Search-InvalidGPOs.ps1:37 Zeichen:71
+ $InvalidGPOs += new-object PSObject -property @{GPOName=“$GPOName“State=“No …
+ ~~~~~~~~~~
Unerwartetes Token „State=“No Permissions““ in Ausdruck oder Anweisung.
In C:\Search-InvalidGPOs.ps1:37 Zeichen:71
+ $InvalidGPOs += new-object PSObject -property @{GPOName=“$GPOName“State=“No …
+ ~
Das Hashliteral war unvollständig.
In C:\Search-InvalidGPOs.ps1:38 Zeichen:4
+ }
+ ~
Unerwartetes Token „}“ in Ausdruck oder Anweisung.
In C:\Search-InvalidGPOs.ps1:39 Zeichen:2
+ }
+ ~
Unerwartetes Token „}“ in Ausdruck oder Anweisung.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
Hallo Frank
Im Script gibt’s noch einen Fehler:
Statt „Get-PPermissions“ steht an einer Stelle „Get-PPermission“