Quick & Dirty: Ungültige und verwaiste Gruppenrichtlinien finden

Ich habe angefangen ein kleines PowerShell Script zu erstellen, welches dabei unterstützen soll ungültige oder verwaiste Gruppenrichtlinien aufzuspüren. Die erste Version des Scripts sucht nach Gruppenrichtlinien die nicht mit einer OU verbunden sind, keine Sicherheitsfilterung besitzen oder alle Einstellungen der GPO deaktiviert wurden. Das Script werde ich zukünftig noch um weitere Funktionen erweitern und als kleines Tool veröffentlichen. Hier aber erst einmal ein Vorgeschmack.

Das Script gibt den Status “Disabled” aus, wenn alle Einstellungen der Richtlinie deaktiviert wurden, hier ein Beispiel:

image

Der Status “Not Linked” wird ausgegeben, wenn die Gruppenrichtlinie mit keiner Organisationseinheit (OU) verknüpft ist, wie hier zu sehen:

image

Wenn die Sicherheitsfilterung leer ist, wird der Status “No Permissions” angegeben:

image

Das Script erfordert das PowerShell Modul “GroupPolicy”, welches Bestandteil ab Windows Server 2008 R2 ist. Getestet habe ich das Script bisher auf Windows Server 2012 R2, Windows Server 2016 und remote via Windows 10.

So sieht die Ausgabe des Scripts in der PowerShell aus:

Ungültige und verwaiste Gruppenrichtlinien finden

Und hier nun das eigentliche Script, welches gerne angepasst oder umgeschrieben werden kann:

$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
	}

Der untere Teil nach #Optional kann verwendet werden, um die gefundenen ungültigen GPOs zu löschen. Das Objekt “$InvalidGPOs” enthält alle gefunden ungültigen GPOs und lässt sich somit auch weiterverwenden.

Vielleicht nützt es ja jemanden.

Update 15.06.2017: Die aktualisierte Version des Scripts listet nun auch leere Gruppenrichtlinien (ohne Einstellungen) und GPOs auf bei denen alle Verknüpfungen zu OUs deaktiviert sind auf.

Update 01.07.2017: Die aktualisierte Version des Scripts berücksichtigt nun auch GPOs die an AD-Standorte verknüpft wurden. Danke an Torsten für den Hinweis.

12 Kommentare zu “Quick & Dirty: Ungültige und verwaiste Gruppenrichtlinien finden”

  1. 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

  2. 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

  3. 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.

    1. 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

    2. Hi Sven,
      die aktualisierte Version findet nun auch leere GPOs und zusätzlich auch GPOs bei denen alle Verknüpfungen deaktiviert sind.
      Gruß,
      Frank

    1. 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

  4. 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…

  5. 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

  6. Hallo Frank

    Im Script gibt’s noch einen Fehler:

    Statt „Get-PPermissions“ steht an einer Stelle „Get-PPermission“

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.