Powershell GPO XML to CSV Parser/Transformer
Wenn mittes Powershell GPO´s ausgewertet oder verglichen werden sollen dann entsteht eine großen Herausforderung.
Die von Microsoft gelieferten XML Daten sind grütze. Es ist nicht möglich Vergleiche oder Reports out of the box zu erstellen.
Genau das war eine Anforderungen in einem Projekt. Eine hohe Anzahl an Domänen mit einigen hundert GPO´s sollten analysiert, verglichen, konsolidieren und anschließend restrukturiert werden.
UPDATE DEZEMBER 2019: Es gibt eine bessere Möglichkeit! Wird hier auf dem Blog irgendwann mal veröffentlich..
Da ich im Internet aber auch so gar nichts brauchbares gefunden habe. Hier die Lösung:
Das Skript ermittelt alle Einstellungen und gruppiert die zusammengehörigen Elemente.
Derzeit werden selbstgeschriebene ADM Settings (ja sowas gibt es noch) ignoriert.
Dieses Skript ist die Kernkomponente eines sehr umfangreichen Tools welches ich entwickelt habe. Das Tool ist in der Lage GPO´s welche auf eine Quell-OU verlinkt sind mit Analyse der Rechte ob deny/Gpo Apply angewandt wird, gegen eine Ziel-OU zu vergleichen und die Unterschiede aufzuzeigen und das auch Domänenübergreifend. Das bedeutet es kann ermittelt werden welche Gruppenrichtline in der neuen OU wirken und ob Einstellungen fehlen. Dieses Skript kann gerne per E-Mail angefordert werden.
function get-gpoxmldata{ param([string]$guid) $globalCount = 0 $global:output = @() function run-recurseinfo { param ($Name,$Node) $Definitions = $Node | Get-Member -MemberType Properties | where { $_.Definition -like "System*" } $ob = ($Node | Get-Member -MemberType Properties | where { $_.Definition -like "string*" }).Name foreach ($o in $ob) { if ($o -eq "xmlns") { break } if ($o -eq "#text") { $Path = $Name } else { $path = "$($Name)\$($o)" } write-db -objcount $globalCount -Path $path -Value $Node.$o } foreach ($Definition in $Definitions) { checknode $Node -Name ("$($Name)\$($Definition.Name)") -Node $Node.($Definition.Name) } } function checknode { param($Node,$Name,$direct) $NodeCount = ($Node | Measure-Object).count if ($NodeCount -gt 1) { for ($j = 0; $j -lt $NodeCount; $j++) { if ($direct -eq $true){$globalCount++} $ob = ($Node[$j] | Get-Member -MemberType Properties | where { $_.Definition -like "string*" }).Name foreach ($o in $ob) { if ($o -eq "xmlns") { break } if ($o -eq "#text") { $Path = $Name } else { $path = "$($Name)\$($o)" } write-db -objcount $globalCount -Path $path -Value $Node[$j].$o } try {$Definitions = $Node[$j] | get-Member -MemberType Properties -ErrorAction Stop} catch{$Definitions = $null} foreach ($Definition in $Definitions) { if ($Definition.Name -notlike "#text") { run-recurseinfo -Name ("$($Name)\$($Definition.Name)") -Node $Node[$j].($Definition.Name) } } } } else {run-recurseinfo -Name $Name -Node $Node } } function write-db { param($GPOKey,$objcount,$Path,[string]$Value) $Value = $Value.replace("'","''") $object = New-Object PSObject Add-Member -InputObject $object -MemberType NoteProperty -Name ObjNr -Value $objcount Add-Member -InputObject $object -MemberType NoteProperty -Name Path -Value $Path Add-Member -InputObject $object -MemberType NoteProperty -Name Value -Value $Value $global:output += $object } function group-result{ param($Array) $outputfunc = @() $lastrunObj = $Null $count = 0 foreach ($obj in $Array) { $spath = ([System.Text.RegularExpressions.Regex]::replace($obj.Path,"^([\w]+)(\\)([\w]+)(\\).+",'${1}${2}${3}')) if ($obj.ObjNr -ne $lastrunObj -or $lastrunpath -ne $spath ) {$count++} if ($obj.Value) { $object = New-Object PSObject Add-Member -InputObject $object -MemberType NoteProperty -Name Group -Value $count Add-Member -InputObject $object -MemberType NoteProperty -Name Path -Value $obj.Path Add-Member -InputObject $object -MemberType NoteProperty -Name Value -Value $obj.Value $outputfunc += $object $lastrunObj = $obj.ObjNr $lastrunpath = $spath } } return $outputfunc } try{ Import-Module GroupPolicy -ErrorAction stop} catch{"Could not Import Module" break} $XML = Get-GPOReport -Guid $guid -ReportType xml $ComputerGPO = $XML.GPO.Computer.ExtensionData $ComputerNodes = $ComputerGPO.Name $UserGPO = $XML.GPO.User.ExtensionData $UserNodes = $UserGPO.Name if ($XML.GPO.Computer.ExtensionData) { #Region Prüfen ob XML-Rückgabe ein Array ist #0 #Analysieren ob der zurückgelieferte Wert ein Array ist oder nicht if (($ComputerGPO.extension | Measure-Object).count -gt 1) {$multivalue = $true} else {$multivalue = $false} #Wenn ein Array dann wird die Variable $ScriptNodes mit allen Werten gefüllt if ($multivalue -eq $true ) { $count = 0 $ScriptNodes = @() foreach ($Node in $ComputerNodes) { $obj = $ComputerGPO[$count].extension | Get-Member -MemberType Properties | where {$_.name -notlike "type" -and $_.name -notlike "q*" } $ScriptNodes += $obj | foreach{"$($_.Name)"} $count++ } } #Wenn kein Array dann wird die Variable $ScriptNodes mit den Werten des Objekt gefüllt if ($multivalue -eq $false) { $ScriptNodes = @() $obj = $ComputerGPO.extension | Get-Member -MemberType Properties -ErrorAction Stop | where {$_.name -notlike "type" -and $_.name -notlike "q*" } $ScriptNodes += $obj | foreach{"$($_.Name)"} } #EndRegion #Region Für jeden in #0 gefunden Wert die Überprüfung starten $ScriptNodes | foreach {checknode -Name "Computer\$($_)" -Node $ComputerGPO.Extension.$_ -direct $true} #EndRegion } if ($XML.GPO.User.ExtensionData) { #Region Prüfen ob XML-Rückgabe ein Array ist #0 #Analysieren ob der zurückgelieferte Wert ein Array ist oder nicht if (($UserGPO.extension | Measure-Object).count -gt 1) {$multivalue = $true} else {$multivalue = $false} #Wenn ein Array dann wird die Variable $ScriptNodes mit allen Werten gefüllt if ($multivalue -eq $true ) { $count = 0 $ScriptNodes = @() foreach ($Node in $UserNodes) { $obj = $UserGPO[$count].extension | Get-Member -MemberType Properties | where {$_.name -notlike "type" -and $_.name -notlike "q*" } $ScriptNodes += $obj | foreach{"$($_.Name)"} $count++ } } #Wenn kein Array dann wird die Variable $ScriptNodes mit den Werten des Objekt gefüllt if ($multivalue -eq $false) { $ScriptNodes = @() $obj = $UserGPO.extension | Get-Member -MemberType Properties -ErrorAction Stop | where {$_.name -notlike "type" -and $_.name -notlike "q*" } $ScriptNodes += $obj | foreach{"$($_.Name)"} } #EndRegion #Region Für jeden in #0 gefunden Wert die Überprüfung starten $ScriptNodes | foreach {checknode -Name "User\$($_)" -Node $UserGPO.Extension.$_ -direct $true} #EndRegion } group-result $global:output } Export-ModuleMember -Function get-gpoxmldata