MAD-GPO.ps1

<###########################
     Group Policy
############################>

# Collecte et analyse des GPO (Group Policy Objects).
#
# Variables consommees (fournies par le contexte Get-MADReport) :
# $nogpomod - flag : si $true, le module GPMC est absent, on saute la collecte
# $DomainControllerobj - objet Get-ADDomain (SubordinateReferences, DistinguishedName)
#
# Variables produites (utilisees par le HTML MultiPage et OnePage) :
# $GPOs - liste brute de tous les GPOs
# $GPO_Linked - GPOs liees a au moins un conteneur
# $GPO_NotLinked - GPOs orphelines (non liees)
# $GPO_Details - GPOs liees enrichies avec colonne 'Linked To' et 'Link Count'

if (!$nogpomod) {
    Write-Host ""
    Write-Host " #=======================================================================" -ForegroundColor DarkCyan
    Write-Host " # [GROUP POLICY OBJECTS (GPO)]" -ForegroundColor Cyan
    Write-Host " #=======================================================================" -ForegroundColor DarkCyan
    Write-Progress-Custom "GPO" "Analyse des strategies de groupe"

    # All GPOs
    $GPOs = Get-GPO -All | Select-Object DisplayName, GPOStatus, Id, ModificationTime, CreationTime, WmiFilter

    # Collect all containers (domain, OUs, sites) that can hold gPLink
    $adContainers = @()
    # BUG21 FIX : .Trim() lève une exception si Where-Object retourne $null (aucun SubordinateReference 'configuration')
    $configuration = ($DomainControllerobj.SubordinateReferences | Where-Object { $_ -like '*configuration*' } | Select-Object -First 1)
    if ($configuration) { $configuration = $configuration.Trim() }
    $adContainers += Get-ADObject -LDAPFilter "(|(objectClass=organizationalUnit)(objectClass=domainDNS))" -SearchBase $DomainControllerobj.DistinguishedName -Properties gpLink, distinguishedName, name
    $adContainers += Get-ADObject -LDAPFilter "(objectClass=site)" -SearchBase $configuration -Properties gpLink, distinguishedName, name

    # Extract linked GPO GUIDs from gPLink strings AND build mapping GPO -> OUs
    $linkedGuids = New-Object 'System.Collections.Generic.HashSet[string]'
    $gpoToOUMapping = @{}  # Dictionary: GPO GUID -> List of OU names
    
    foreach ($c in $adContainers) {
        $gp = $c.gpLink
        if ([string]::IsNullOrEmpty($gp)) { continue }
        
        foreach ($m in [regex]::Matches($gp, '\{([0-9a-fA-F-]{36})\}')) {
            $guid = $m.Groups[1].Value.ToLower()
            [void]$linkedGuids.Add($guid)
            
            # Build mapping GPO -> OU
            if (-not $gpoToOUMapping.ContainsKey($guid)) {
                $gpoToOUMapping[$guid] = New-Object 'System.Collections.Generic.List[string]'
            }
            
            # Use friendly name (OU name or Domain or Site)
            $containerName = if ($c.objectClass -eq 'domainDNS') {
                "Domain Root"
            } elseif ($c.objectClass -eq 'site') {
                "Site: $($c.name)"
            } else {
                $c.name
            }
            
            $gpoToOUMapping[$guid].Add($containerName)
        }
    }

    # Split GPOs into linked and not linked, and add LinkedTo info
    $GPO_Linked    = New-Object 'System.Collections.Generic.List[Object]'
    $GPO_NotLinked = New-Object 'System.Collections.Generic.List[Object]'
    $GPO_Details   = New-Object 'System.Collections.Generic.List[Object]'  # Enhanced table with LinkedTo

    foreach ($g in $GPOs) {
        $gid = $g.Id.Guid.ToString().ToLower()
        
        if ($linkedGuids.Contains($gid)) {
            $GPO_Linked.Add($g)
            
            # Create enhanced object with LinkedTo information
            $linkedToList = $gpoToOUMapping[$gid] -join ", "
            $linkedCount = $gpoToOUMapping[$gid].Count
            
            $enhancedGPO = [PSCustomObject]@{
                'GPO Name'   = $g.DisplayName
                'Status'     = $g.GPOStatus
                'Linked To'  = $linkedToList
                'Link Count' = $linkedCount
                'Modified'   = $g.ModificationTime
                'Created'    = $g.CreationTime
                'WMI Filter' = if ($g.WmiFilter) { $g.WmiFilter.Name } else { "None" }
            }
            $GPO_Details.Add($enhancedGPO)
            
        } else {
            $GPO_NotLinked.Add($g)
        }
    }

    # Make sure NotLinked is always an array (never integer), so .Count is reliable
    if (-not $GPO_NotLinked) { $GPO_NotLinked = @() }

    # ============================================================
    # GPO LIEES AUX DOMAIN CONTROLLERS (visibles avec -ShowSensitiveObjects)
    # ============================================================
    # Identifier les conteneurs qui correspondent a l'OU Domain Controllers
    $GPO_DCLinked = New-Object 'System.Collections.Generic.List[Object]'
    $dcOUKeywords = @("Domain Controllers", "DomainControllers", "DC", "Controllers")

    foreach ($gpoDetail in $GPO_Details) {
        $linkedTo = $gpoDetail.'Linked To'
        if ([string]::IsNullOrEmpty($linkedTo)) { continue }

        # Verifier si la GPO est liee a une OU dont le nom evoque les DC
        $isDCLinked = $false
        foreach ($kw in $dcOUKeywords) {
            if ($linkedTo -match [regex]::Escape($kw)) {
                $isDCLinked = $true
                break
            }
        }
        # Ajouter un flag sur l'objet existant
        $gpoDetail | Add-Member -NotePropertyName 'Linked To DC OU' -NotePropertyValue $isDCLinked -Force
        if ($isDCLinked) {
            $GPO_DCLinked.Add($gpoDetail)
        }
    }

    # Ajouter le flag sur les GPOs non enrichies (GPO_Details les a deja)
    if ($ShowSensitiveObjects.IsPresent) {
        Write-Progress-Custom "GPO" "Detection des GPO liees aux Domain Controllers"
    }

    Write-Success "GPO collectees : $($GPO_Linked.Count) liee(s), $($GPO_NotLinked.Count) orpheline(s), $($GPO_DCLinked.Count) liee(s) aux DC"
    $_gpoLinked = $GPO_Linked.Count
    $_gpoOrph   = $GPO_NotLinked.Count
    $_gpoTotal  = $GPOs.Count
    Write-Host " >> Liees: $_gpoLinked | Orphelines: $_gpoOrph | Total: $_gpoTotal" -ForegroundColor DarkGray
}