Skip to main content

Script Master

Todoagdlp.png

Introduction

L'ensemble des scripts vu précédemment est découpé d'une façon logique.
Ainsi, il est possible de modifier le fichier source CSV et relancer les scripts sans devoir relancer toute la machine.

Pour simplifier encore plus tout cela, on peut faire un script master qui gère tout ça.
Voici ce qu'il peut faire :

    Lancer tout les script déjà vu Lancer chaque étape individuellement Préparer le CSV (nettoyage, tri, export)

    Powershell

    Voici donc le script :

    <#
    ===============================================================================
    SCRIPT : 0 - Maitre_AGDLP.ps1
    OBJET  : Piloter l'ensemble du deploiement AGDLP + utilitaires CSV GG
    ===============================================================================
    
    FONCTIONS MAJEURES
    ------------------
    - Matrice de progression multi-etapes (barre ASCII 40)
    - Validations souples (colonnes minimales uniquement avant 1.1/2.1)
    - Menu :
        1) Tout lancer (1.1 -> 2.1 -> 3.1 -> 4.1 -> 5.1 -> 6.1)
        2..7) Etapes individuelles
        8) Audit / Dry-run (dossiers, groupes AD manquants)
        9) Preparer CSV GG (nettoyage + tri topologique + export)
       10) Quitter
    - Audit GG : groupes references (dans Arbo) introuvables dans AD, suggestions
    - Sorties visibles sans accents (compatibilite), commentaires en francais
    
    REMARQUES
    ---------
    - Les scripts 1.1 a 6.1 doivent exister dans $ScriptsDir
    
    ===============================================================================
    #>
    
    Param(
        [string]$CsvArbo     = "C:\Scripts\Arbo\LCC_Arbo_DATAS.csv",
        [string]$CsvGG       = "C:\Scripts\Arbo\GG_LCC_SVC.csv",
        [string]$Share       = "\\10.0.10.182\datas\",
        [string]$BaseOU_GDL  = "OU=GDL,OU=AGDLP,DC=ccpl,DC=local",
        [string]$BaseOU_GG   = "OU=GG,OU=AGDLP,DC=ccpl,DC=local",
        [string]$ScriptsDir  = "C:\Scripts\Arbo",
        [string]$OutDir      = "C:\Scripts\Arbo\Out"
    )
    
    $ErrorActionPreference = "Stop"
    
    # ============================================================================
    #  UTILITAIRES AFFICHAGE (barre ASCII 40 + matrice)
    # ============================================================================
    $AsciiBarLen = 40
    
    function New-AsciiBar {
        param([double]$Percent)
        $Percent = [Math]::Max(0, [Math]::Min(100, $Percent))
        $filled  = [Math]::Floor(($Percent/100) * $AsciiBarLen)
        if ($filled -gt $AsciiBarLen) { $filled = $AsciiBarLen }
        $hash = "#" * $filled
        $dash = "-" * ($AsciiBarLen - $filled)
        return "[{0}{1}] {2}%" -f $hash, $dash, ([Math]::Round($Percent))
    }
    
    # Etapes
    $Steps = @(
        @{ Id=1; Name="1/6  Arborescence";   File="1.1-Creation_Arborescence.ps1"; Args={@("-CsvPath `"$CsvArbo`"","-Destination `"$Share`"")};  State="EN ATTENTE"; Pct=0 },
        @{ Id=2; Name="2/6  GDL + OU";       File="2.1-Creation_GDL.ps1";          Args={@("-CsvPath `"$CsvArbo`"","-BaseOU `"$BaseOU_GDL`"")};  State="EN ATTENTE"; Pct=0 },
        @{ Id=3; Name="3/6  Parentalite";    File="3.1-Creation_Parentalite.ps1";  Args={@("-CsvPath `"$CsvArbo`"")};                            State="EN ATTENTE"; Pct=0 },
        @{ Id=4; Name="4/6  ACL";            File="4.1-Appliquer_ACL.ps1";         Args={@("-RootPath `"$Share`"","-CsvPath `"$CsvArbo`"")};    State="EN ATTENTE"; Pct=0 },
        @{ Id=5; Name="5/6  Creation GG";    File="5.1-Creation_GG.ps1";           Args={@("-CSVPath `"$CsvGG`"","-OUPath `"$BaseOU_GG`"")};    State="EN ATTENTE"; Pct=0 },
        @{ Id=6; Name="6/6  GG -> GDL";      File="6.1-Ajout_GG_dans_GDL.ps1";      Args={@("-CsvPath `"$CsvArbo`"")};                           State="EN ATTENTE"; Pct=0 }
    )
    
    function Draw-Matrix {
        Clear-Host
        Write-Host "===================== PROGRESSION AGDLP ====================="
        foreach ($s in $Steps) {
            $bar = New-AsciiBar -Percent $s.Pct
            Write-Host ("{0,-26} {1,-4} {2}" -f $s.Name, $s.State, $bar)
        }
        Write-Host "============================================================="
    }
    
    # ============================================================================
    #  OUTILS VALIDATION / AUDIT
    # ============================================================================
    
    function Test-Admin {
        return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
        ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    }
    
    function Require-ModuleAD {
        try { Import-Module ActiveDirectory -ErrorAction Stop; return $true }
        catch { Write-Warning "Module ActiveDirectory manquant."; return $false }
    }
    
    function Test-CsvColumns {
        param([string]$Path,[string[]]$RequiredCols)
        if (!(Test-Path $Path)) { return @("Fichier introuvable: $Path") }
        $rows = Import-Csv -Path $Path -Delimiter ';' -Encoding UTF8
        if (!$rows) { return @("CSV vide: $Path") }
        $cols = ($rows | Select-Object -First 1).PSObject.Properties.Name
        $missing = $RequiredCols | Where-Object { $_ -notin $cols }
        return $missing
    }
    
    # Nettoyage leger cellules (NBSP, ZWSP, guillemets typographiques, espaces)
    function Clean-Cell([string]$s) {
        if (-not $s) { return "" }
        $s = $s -replace "&nbsp;", " "
        $s = $s -replace "\u00A0", " "
        $s = $s -replace "\u200B", ""
        $s = $s -replace "[«»“”‘’]", ""
        return ($s -replace "\s+", " ").Trim()
    }
    
    # Calcul distance d'edition (Levenshtein) 100% ASCII
    function Get-EditDistance {
        param(
            [string]$a,
            [string]$b
        )
    
        if ($null -eq $a) { $a = "" }
        if ($null -eq $b) { $b = "" }
    
        $la = $a.Length
        $lb = $b.Length
    
        # matrice (la+1) x (lb+1)
        $d = New-Object 'int[,]' ($la + 1), ($lb + 1)
    
        for ($i = 0; $i -le $la; $i++) { $d[$i,0] = $i }
        for ($j = 0; $j -le $lb; $j++) { $d[0,$j] = $j }
    
        for ($i = 1; $i -le $la; $i++) {
            for ($j = 1; $j -le $lb; $j++) {
    
                # PAS de ternaire ici. If clair et propre :
                if ($a[$i-1] -eq $b[$j-1]) {
                    $cost = 0
                }
                else {
                    $cost = 1
                }
    
                $delete  = $d[$i-1, $j] + 1
                $insert  = $d[$i, $j-1] + 1
                $replace = $d[$i-1, $j-1] + $cost
    
                $d[$i,$j] = [Math]::Min([Math]::Min($delete, $insert), $replace)
            }
        }
    
        return $d[$la,$lb]
    }
    # Validation generale legere
    function Validate-Core {
        $ok=$true
        if (!(Test-Admin)) { Write-Warning "Doit etre lance en administrateur."; $ok=$false }
        if (!(Test-Path $Share)) { Write-Warning "Partage introuvable: $Share"; $ok=$false }
        if (!(Require-ModuleAD)) { $ok=$false }
        if (!(Test-Path $CsvArbo)) { Write-Warning "CSV Arbo introuvable: $CsvArbo"; $ok=$false }
        if (!(Test-Path $CsvGG)) { Write-Warning "CSV GG introuvable: $CsvGG"; $ok=$false }
        if (!(Test-Path $ScriptsDir)) { Write-Warning "ScriptsDir introuvable: $ScriptsDir"; $ok=$false }
    
        $reqMin = @("NIVEAU 1","NIVEAU 2","NIVEAU 3","NIVEAU 4","NIVEAU 5","NIVEAU 6","NIVEAU 7","NIVEAU 8","CREER_GROUPES")
        $missMin = Test-CsvColumns -Path $CsvArbo -RequiredCols $reqMin
        if ($missMin.Count -gt 0) { Write-Warning "Colonnes minimales manquantes (Arbo): $($missMin -join ', ')"; $ok=$false }
    
        return $ok
    }
    
    # Validation specifique par etape (souple)
    function Validate-ForStep {
        param([ValidateSet("ARBO","GDL","PARENT","ACL","GG","GGtoGDL","PREP_GG","AUDIT")] [string]$Step)
    
        switch ($Step) {
            "ARBO"   { return $true }
            "GDL"    { return $true }
            "PARENT" { 
                $need=@("FolderPath","GDL_ECRITURE","GDL_LECTURE","GDL_VUE","Parent_GDL")
                $missing = Test-CsvColumns -Path $CsvArbo -RequiredCols $need
                if ($missing.Count -gt 0) { Write-Warning "Manque pour 3.1: $($missing -join ', ')"; 
                    $ans=Read-Host "Continuer quand meme ? (O/N)"; return ($ans -match '^(?i)o') }
                return $true
            }
            "ACL"    {
                $need=@("FolderPath","GDL_ECRITURE","GDL_LECTURE","GDL_VUE")
                $missing = Test-CsvColumns -Path $CsvArbo -RequiredCols $need
                if ($missing.Count -gt 0) { Write-Warning "Manque pour 4.1: $($missing -join ', ')";
                    $ans=Read-Host "Continuer quand meme ? (O/N)"; return ($ans -match '^(?i)o') }
                return $true
            }
            "GG"     {
                $need=@("GG","Membre")
                $missing = Test-CsvColumns -Path $CsvGG -RequiredCols $need
                if ($missing.Count -gt 0) { Write-Warning "Manque pour 5.1: $($missing -join ', ')";
                    $ans=Read-Host "Continuer quand meme ? (O/N)"; return ($ans -match '^(?i)o') }
                return $true
            }
            "GGtoGDL" {
                $need=@("FolderPath","GDL_ECRITURE","GDL_LECTURE")
                $missing = Test-CsvColumns -Path $CsvArbo -RequiredCols $need
                if ($missing.Count -gt 0) { Write-Warning "Manque pour 6.1: $($missing -join ', ')";
                    $ans=Read-Host "Continuer quand meme ? (O/N)"; return ($ans -match '^(?i)o') }
                return $true
            }
            "PREP_GG" { return $true }
            "AUDIT"   { return $true }
        }
    }
    
    # ============================================================================
    #  LANCEMENT D'UNE ETAPE
    # ============================================================================
    function Run-Step {
        param([ValidateSet("ARBO","GDL","PARENT","ACL","GG","GGtoGDL")] [string]$Step)
    
        if (-not (Validate-ForStep $Step)) { return }
    
        # recuperer la structure etape
        $s = switch ($Step) {
            "ARBO"    { $Steps[0] }
            "GDL"     { $Steps[1] }
            "PARENT"  { $Steps[2] }
            "ACL"     { $Steps[3] }
            "GG"      { $Steps[4] }
            "GGtoGDL" { $Steps[5] }
        }
    
        $s.State = "EN COURS"; $s.Pct = 10; Draw-Matrix
    
        $path = Join-Path $ScriptsDir $s.File
        if (!(Test-Path $path)) {
            $s.State="ERREUR"; $s.Pct=0; Draw-Matrix
            Write-Warning "Script introuvable: $path"
            return
        }
    
        try {
            & powershell.exe -ExecutionPolicy Bypass -File $path @(& $s.Args)
            $s.State = "TERMINE"; $s.Pct = 100; Draw-Matrix
        }
        catch {
            $s.State = "ERREUR"; $s.Pct = 0; Draw-Matrix
            Write-Warning "Erreur a l'execution: $path -> $_"
        }
    }
    
    # ============================================================================
    #  AUDIT / DRY-RUN
    # ============================================================================
    function Audit-Only {
        Write-Host ""
        Write-Host "=== AUDIT (dry-run) ==="
    
        # 1) Dossiers manquants
        $rows = Import-Csv -Path $CsvArbo -Delimiter ';' -Encoding UTF8
        $missingFolders = @()
        foreach ($r in $rows) {
            if ([string]::IsNullOrWhiteSpace($r.FolderPath)) { continue }
            $fp = Join-Path $Share $r.FolderPath
            if (!(Test-Path -LiteralPath $fp)) { $missingFolders += $fp }
        }
        Write-Host "Dossiers manquants : $($missingFolders.Count)"
        if ($missingFolders.Count -gt 0) { $missingFolders | Select-Object -Unique | ForEach-Object { "  - $_" } }
    
        # 2) Groupes GDL manquants (si colonnes presentes)
        $cols = ($rows | Select-Object -First 1).PSObject.Properties.Name
        if (@("GDL_ECRITURE","GDL_LECTURE","GDL_VUE","Parent_GDL") | ForEach-Object { $_ -in $cols } | Where-Object {$_} | Measure-Object | Select-Object -ExpandProperty Count) {
            $needGroups = @()
            foreach ($r in $rows) {
                foreach ($g in @($r.GDL_ECRITURE,$r.GDL_LECTURE,$r.GDL_VUE,$r.Parent_GDL)) {
                    if ([string]::IsNullOrWhiteSpace($g)) { continue }
                    if (!(Get-ADGroup -Filter "Name -eq '$g'" -ErrorAction SilentlyContinue)) { $needGroups += $g }
                }
            }
            Write-Host "GDL manquants : $($needGroups.Count)"
            if ($needGroups.Count -gt 0) { $needGroups | Select-Object -Unique | ForEach-Object { "  - $_" } }
        } else {
            Write-Host "(Colonnes GDL_* absentes pour l’instant — lancer 2.1 pour les creer.)"
        }
    
        # 3) Audit GG references dans CSV Arbo vs AD
        $ggRefs = @()
        foreach ($r in $rows) {
            foreach ($cell in @($r.GG_ECRITURE,$r.GG_LECTURE)) {
                $cell = Clean-Cell $cell
                if (-not $cell) { continue }
                $ggRefs += ($cell.Split(",") | ForEach-Object { Clean-Cell $_ } | Where-Object { $_ -ne "" })
            }
        }
        $ggRefs = $ggRefs | Select-Object -Unique
        $missingGG = @()
        foreach ($g in $ggRefs) {
            if (-not (Get-ADGroup -Identity $g -ErrorAction SilentlyContinue)) { $missingGG += $g }
        }
        Write-Host "GG references mais introuvables dans AD : $($missingGG.Count)"
        if ($missingGG.Count -gt 0) {
            # suggestions
            $allKnown = Get-ADGroup -LDAPFilter "(cn=LCC_GG_*)" -SearchScope Subtree -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name
            foreach ($m in $missingGG) {
                $best=""; $bestScore=9999
                foreach ($k in $allKnown) {
                    $d = Get-EditDistance $m $k
                    if ($d -lt $bestScore) { $bestScore=$d; $best=$k }
                }
                if ($best) { Write-Host ("  - {0}  (suggestion: {1})" -f $m, $best) } else { Write-Host ("  - {0}" -f $m) }
            }
        }
        Read-Host "Fin audit - Entrer pour continuer" | Out-Null
    }
    
    # ============================================================================
    #  PREPARATION CSV GG (nettoyage + tri topologique + export)
    # ============================================================================
    
    function Prepare-GG-CSV {
        if (!(Test-Path $CsvGG)) { Write-Warning "CSV GG introuvable: $CsvGG"; return }
    
        if (!(Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir -Force | Out-Null }
        $out = Join-Path $OutDir "GG_LCC_SVC.sorted.csv"
    
        Write-Host "Lecture et nettoyage de $CsvGG ..."
        $df = Import-Csv -Path $CsvGG -Delimiter ';' | ForEach-Object {
            [pscustomobject]@{
                GG     = (Clean-Cell $_.GG)
                Membre = (Clean-Cell $_.Membre)
            }
        }
    
        # Supprimer lignes totalement vides
        $df = $df | Where-Object { $_.GG -or $_.Membre }
    
        # Construire dependances : parent depend de chaque Membre *_GG_*
        $parents = @{}
        $refs    = @{}
        foreach ($r in $df) {
            if (-not $r.GG) { continue }
            if (-not $parents.ContainsKey($r.GG)) { $parents[$r.GG] = New-Object System.Collections.Generic.HashSet[string] ([StringComparer]::OrdinalIgnoreCase) }
            if ($r.Membre -and $r.Membre -like "*_GG_*" -and $r.Membre -ne $r.GG) {
                if (-not $refs.ContainsKey($r.Membre)) { $refs[$r.Membre] = $true }
                [void]$parents[$r.GG].Add($r.Membre)
            }
        }
    
        # Ensemble des noeuds
        $all = New-Object System.Collections.Generic.HashSet[string] ([StringComparer]::OrdinalIgnoreCase)
        foreach ($k in $parents.Keys) { [void]$all.Add($k) }
        foreach ($k in $refs.Keys)    { [void]$all.Add($k) }
    
        # Calcul indegree pour Kahn
        $indeg = @{}
        foreach ($n in $all) { $indeg[$n] = 0 }
        foreach ($p in $parents.Keys) {
            foreach ($c in $parents[$p]) {
                if (-not $indeg.ContainsKey($c)) { $indeg[$c] = 0 }
                $indeg[$p] += 0 # ensure key
                $indeg[$p] = $indeg[$p]       # parent indeg sera incremente ci-dessous
            }
        }
        # indegree = nb de dependances (c -> p) : parent a +1 par enfant reference
        foreach ($p in $parents.Keys) {
            foreach ($c in $parents[$p]) {
                $indeg[$p] = $indeg[$p] + 1
            }
        }
    
        # File des noeuds indeg 0
        $queue = New-Object System.Collections.Queue
        foreach ($n in $all) { if ($indeg[$n] -eq 0) { $queue.Enqueue($n) } }
    
        $order = New-Object System.Collections.ArrayList
        while ($queue.Count -gt 0) {
            $n = $queue.Dequeue()
            [void]$order.Add($n)
            foreach ($p in $parents.Keys) {
                if ($parents[$p].Contains($n)) {
                    $indeg[$p] = $indeg[$p] - 1
                    [void]$parents[$p].Remove($n)
                    if ($indeg[$p] -eq 0) { $queue.Enqueue($p) }
                }
            }
        }
    
        # Cycles residuels
        $cyclic = @()
        foreach ($n in $indeg.Keys) { if ($indeg[$n] -gt 0) { $cyclic += $n } }
    
        # Reconstruire CSV trie : pour chaque GG dans l'ordre topo, garder ses lignes
        $declares = ($df | Select-Object -ExpandProperty GG -Unique)
        $blocks = New-Object System.Collections.ArrayList
        foreach ($g in $order) {
            if ($declares -contains $g) {
                $blk = $df | Where-Object { $_.GG -eq $g }
                foreach ($r in $blk) { [void]$blocks.Add($r) }
            }
        }
        # Ajouter ceux eventuellement manques
        foreach ($g in $declares) {
            if (-not ($order -contains $g)) {
                $blk = $df | Where-Object { $_.GG -eq $g }
                foreach ($r in $blk) { [void]$blocks.Add($r) }
            }
        }
    
        # Export
        $blocks | Export-Csv -Path $out -Delimiter ';' -NoTypeInformation -Encoding UTF8
        Write-Host "CSV GG trie ecrit : $out"
    
        if ($cyclic.Count -gt 0) {
            Write-Warning "Attention: dependances cycliques detectees:"
            $cyclic | Select-Object -Unique | ForEach-Object { "  - $_" }
        }
    
        $ans = Read-Host "Remplacer le CSV GG d'origine par la version triee ? (O/N)"
        if ($ans -match '^(?i)o') {
            Copy-Item -Path $out -Destination $CsvGG -Force
            Write-Host "Remplacement effectue."
        }
        Read-Host "Fin preparation GG - Entrer pour continuer" | Out-Null
    }
    
    # ============================================================================
    #  MENU PRINCIPAL
    # ============================================================================
    function Main-Menu {
        do {
            Draw-Matrix
            Write-Host ""
            Write-Host "=== MENU AGDLP ==="
            Write-Host "1) Tout lancer (1.1 -> 2.1 -> 3.1 -> 4.1 -> 5.1 -> 6.1)"
            Write-Host "2) 1.1 - Creer arborescence"
            Write-Host "3) 2.1 - Creer GDL + OU"
            Write-Host "4) 3.1 - Parentalite GDL"
            Write-Host "5) 4.1 - Appliquer ACL"
            Write-Host "6) 5.1 - Creer GG"
            Write-Host "7) 6.1 - Ajouter GG -> GDL"
            Write-Host "8) Audit / Dry-run"
            Write-Host "9) Preparer CSV GG (nettoyage + tri topo)"
            Write-Host "10) Quitter"
    
            $coreOK = Validate-Core
            $choice = Read-Host "Choix"
    
            if (-not $coreOK -and $choice -notin @('8','9','10')) {
                Write-Warning "Pre-requis generaux non valides. Corriger puis relancer."
                Read-Host "Entrer pour continuer" | Out-Null
                continue
            }
    
            switch ($choice) {
                '1' { Run-Step ARBO; Run-Step GDL; Run-Step PARENT; Run-Step ACL; Run-Step GG; Run-Step GGtoGDL; Read-Host "Flux complet termine - Entrer pour continuer" | Out-Null }
                '2' { Run-Step ARBO;     Read-Host "Entrer pour continuer" | Out-Null }
                '3' { Run-Step GDL;      Read-Host "Entrer pour continuer" | Out-Null }
                '4' { Run-Step PARENT;   Read-Host "Entrer pour continuer" | Out-Null }
                '5' { Run-Step ACL;      Read-Host "Entrer pour continuer" | Out-Null }
                '6' { Run-Step GG;       Read-Host "Entrer pour continuer" | Out-Null }
                '7' { Run-Step GGtoGDL;  Read-Host "Entrer pour continuer" | Out-Null }
                '8' { Audit-Only }
                '9' { Prepare-GG-CSV }
                '10' { return }
                default { }
            }
        } while ($true)
    }
    
    # Lancement
    Main-Menu