# ------------------------------------------------------------------ # 0. Load modules (they are usually loaded by default, but be safe) # ------------------------------------------------------------------ Import-Module GroupPolicy -ErrorAction Stop Import-Module ActiveDirectory -ErrorAction Stop $TargetOU = "OU=NA,OU=Manufacturing,OU=Tesla Systems,DC=teslamotors,DC=com" $OutputFile = "C:\Users\matanderson\gpo.yaml" # Add-Content -Path $OutputFile -Value $line if (Test-Path $OutputFile) { Remove-Item -Path $OutputFile -Force } # ------------------------------------------------------------------ # 1. Grab every OU under $TargetOU (including $TargetOU itself) # ------------------------------------------------------------------ Write-Output "Enumerating OUs under '$TargetOU' ..." Add-Content -Path $OutputFile -Value "---" Add-Content -Path $OutputFile -Value "- root_ou: $TargetOU" $ouObjects = Get-ADObject ` -Filter 'ObjectClass -eq "organizationalUnit"' ` -SearchBase $TargetOU ` -SearchScope Subtree ` -Properties gPLink # Also grab the target OU itself (in case it is linked directly) $targetOUObj = Get-ADObject ` -Identity $TargetOU ` -Properties gPLink if ($targetOUObj) { $ouObjects += $targetOUObj } # ------------------------------------------------------------------ # 2. Build a hashtable: GPO_GUID => list of OU DNs it is linked to # This is entirely opaque voodoo, i have no idea what's going on # ------------------------------------------------------------------ $gpoLinks = @{} foreach ($ou in $ouObjects) { if ($ou.gPLink) { foreach ($link in ($ou.gPLink -split ';')) { $linkTrim = $link.Trim('[', ']') if ($linkTrim -match '\{(?[0-9a-fA-F-]+)\}') { $guid = $Matches.guid if (-not $gpoLinks.ContainsKey($guid)) { $gpoLinks[$guid] = @() } $gpoLinks[$guid] += $ou.DistinguishedName } } } } if (-not $gpoLinks.Count) { Write-Host "No GPOs linked under the searched OU tree." -ForegroundColor Yellow exit } Write-Host "Found $($gpoLinks.Count) distinct GPO(s) linked in the OU tree." Add-Content -Path $OutputFile -Value " gpo_links: $($gpoLinks.Count)" # ---------------------------------------------------- # 3. For each GPO, get an XML report and pull RestrictedGroup nodes # ---------------------------------------------------- Add-Content -Path $OutputFile -Value " configured_gpos:" foreach ($gpoGuid in $gpoLinks.Keys) { try { $gpo = Get-GPO -Guid $gpoGuid -ErrorAction Stop } catch { Write-Warning "Unable to retrieve GPO $gpoGuid – skipping." continue } Write-Host "GPO: $($gpo.DisplayName)" Add-Content -Path $OutputFile -Value " $($gpo.DisplayName):" # Pull the XML report try { $xmlString = Get-GPOReport -Guid $gpoGuid -ReportType Xml -ErrorAction Stop } catch { Write-Warning "Unable to generate XML report for $($gpo.DisplayName)." continue } $xml = [xml]$xmlString $ns = New-Object System.Xml.XmlNamespaceManager ($xml.NameTable) $ns.AddNamespace('m', 'http://www.microsoft.com/GroupPolicy/Settings') # default namespace $ns.AddNamespace('q1', 'http://www.microsoft.com/GroupPolicy/Settings/Security') $ns.AddNamespace('t', 'http://www.microsoft.com/GroupPolicy/Types') $ns.AddNamespace('xsi','http://www.w3.org/2001/XMLSchema-instance') # Find every element $restrictedGroups = $xml.SelectNodes('//q1:RestrictedGroups', $ns) # Loop and build the string foreach ($rg in $restrictedGroups) { $adGroupName = ($rg.SelectSingleNode('q1:GroupName/t:Name', $ns)).InnerText $localGroupName = ($rg.SelectSingleNode('q1:Memberof/t:Name', $ns)).InnerText $plainAD_Groupname = ($adGroupName -split '\\')[-1] Write-Output "AD Group: $adGroupName - Local Group: $localGroupName" Add-Content -Path $OutputFile -Value " - ad_group: $plainAD_Groupname" Add-Content -Path $OutputFile -Value " local_group: $localGroupName" } Add-Content -Path $OutputFile -Value "" } Add-Content -Path $OutputFile -Value "..."