#Script créé par Nicolas Lang - Sous licence CC-BY-SA #https://nicolaslang.fr ############################################### #region VARIABLES #Emplacement où seront sauvegardés les fichiers $workspace = "\\serveur\GPO_check" #nom du fichier HTML qui sera envoyé par mail $htmlfilename = "results.html" #informations du serveur de messagerie et d'envoi de mail $mailserver = "serveurmail.societe.fr" $usessl = $false $smtpport = 25 $from = "moi@societe.fr" $to = "toi@societe.fr" #Si besoin est : credentials utilisés pour la sauvegarde des fichiers HTML issus des GPO. N'oubliez pas de les sécuriser en mettant le tout dans un #fichier haché $username = "user" $password = "password" #emplacement de sauvegarde des fichiers HTML $backuplocation = "\\serveur\GPOS_HTML\" #Encodage du texte. UTF8 est toujours un bon choix. Ou presque. $encoding = "UTF8" #Nom de fichier de réference pour les anciennes GPO. Inutile de le changer. $gpolistfilename = "oldgpo.clixml" #nombre de lignes récupérées avant et après chaque modification trouvée dans les GPO $maxgathering = 5 #endregion try { #Ajout de la DLL diffmatchpatch qui comparera les fichiers XML des gpo Add-type -path '\\serveur\chemin\vers\DiffMatchPatch.dll' -ErrorAction Stop } catch { throw "Impossible d'ajouter la DLL diffmatchpatch" exit 10 } #On récupère toutes les GPO. Si ça ne fonctionne pas, on bloque le script. (module manquant?) try { $gpos = get-gpo -All -ErrorAction Stop } catch { throw "Impossible de récuperer les GPOS. Module AD manquant?!" exit 1 } Set-Variable DebugPreference -Value continue try { Write-Debug "Déplacement du répertoire vers $workspace" Set-Location $workspace -ErrorAction Stop } catch { Write-Output "Une erreur s'est produite lors de l'accès à $workspace. $($Error[0].exception.message)" Start-Sleep -Seconds 5 exit 3 } $date = get-date -Format "yyyyMMddHHmmss" $errors = New-Object System.Collections.ArrayList $infos = New-Object System.Collections.ArrayList #Cette fonction est utilisée pour parser et mettre en forme les différences de l'outil fc.exe function Highlight-Differences { param ($originalfile,$differences) Begin { $highlightarray = New-Object System.Collections.ArrayList try { Write-Debug "Récupération du contenu du fichier original" $old = Get-Content $originalfile -ErrorAction Stop Write-Debug "Récupération du contenu du fichier de différences" $new = Get-Content $differences -ErrorAction Stop } catch { Write-Debug "Erreur de lecture du fichier, fin du script" throw("Erreur lors de la lecture du fichier source") exit } } Process { # Creation d'un objet diffmatchpatch via la dll (générée depuis le fichier .cs) $diff = new-object DiffMatchPatch.diff_match_patch #création de l'objet de différences $differences = $diff.diff_main($old,$new,$true) #nettoyage sémantique pour lecture plus aisée $diff.diff_cleanupSemantic($differences) #Création du fichier HTML qui sera parsé ensuite $textdiffs = $diff.diff_prettyHtml($differences).replace("> <",">`n<").split("`n") #parsing de l'ensemble du fichier et récupération des lignes comportant un élément ajouté par diff_match_patch $indexarray = New-Object System.Collections.ArrayList $temparray = New-Object System.Collections.ArrayList $gatheringtrigger = $false #décompte du numéro de ligne $index = 0 $textdiffs | % { if ($_ -like "**" -or $_ -like "**") { if ($index -notin $indexarray) { if (($index - $maxgathering) -lt 0) { $start = 0 } else { $start = $index - $maxgathering } $end = $index + 1 $count = 0 do { $count++ $end++ if ($textdiffs[$end] -like "**" -or $textdiffs[$end] -like "**") { $count = 0 } } until ($count -gt $maxgathering -or $end -eq $textdiffs.Length) for($start=$start;$start -le $end;$start++) { $null = $indexarray.Add($start) $null = $temparray.Add("$($start+$maxgathering) : $($textdiffs[$start])
") } $null = $temparray.Add("******************************************************
") } } $index++ } return $temparray } } #On récupère tous les fichiers de GPO marqués _new.xml (faits lors de la dernière exécution du script) pour les renommer en date du jour foreach ($item in (Get-ChildItem $workspace -Filter "*_new.xml")) { try { Write-Debug "Renommage du fichier de $($item.name) vers $($item.name.replace("_new.xml","_$date.xml"))" Move-Item $item.Name ($item.Name.Replace("_new.xml","_$date.xml")) -ErrorAction Stop -Force } catch [System.Management.Automation.ItemNotFoundException] { $errors.Add([pscustomobject]@{ "Fichier" = $item.Name "Type" = [System.Management.Automation.ItemNotFoundException].name "Action" = "Déplacement" "Erreur" = "Impossible de le renommer en date du jour, $($Error[0].exception.message)" }) } catch { $errors.Add([pscustomobject]@{ "Fichier" = $item.Name "Type" = [System.Management.Automation.ItemNotFoundException].name "Action" = "Déplacement" "Erreur" = "Impossible de le renommer en date du jour, $($Error[0].exception.message)" }) } } $sendmail = $false $checkoldgpos = $true try { Write-Debug "Import du fichier CLIXML de la liste des noms de gpo" $oldgpolist = Import-Clixml $gpolistfilename -ErrorAction Stop } catch { $checkoldgpos = $false } if ($checkoldgpos -eq $true) { #On vérifie les gpo crées ou disparues $gpochangelist = Compare-Object -ReferenceObject $oldgpolist.displayname -DifferenceObject $gpos.displayname foreach ($entry in $gpochangelist) { Write-Debug "Recherche de création ou suppression de $($entry.inputobject)" if ($entry.sideindicator -eq "=>") { Write-Debug "GPO $($entry.inputobject) crée" $infos.Add([pscustomobject]@{ "GPO" = $entry.inputobject "Information" = "La GPO a été créée" }) | Out-Null } else { Write-Debug "GPO $($entry.inputobject) supprimée" $infos.Add([pscustomobject]@{ "GPO" = $entry.inputobject "Information" = "La GPO a été supprimée" }) | Out-Null } } } Write-Debug "Export de la liste des GPO" $gpos | Export-Clixml $gpolistfilename Write-Debug "Filtrage de la liste des GPO qui ne se terminent pas par _new.xml et suppression de la date en fin de nom de fichier" $gpolist = (Get-ChildItem -Filter "*.xml" | Where-Object {$_.name -notlike "*_new.xml"}).Name -Replace("_\d{$($date.Length)}.xml$","") | Sort-Object -Unique foreach ($gpo in $gpos) { Write-Debug "Création d'un rapport HTML pour $($gpo.displayname)" $gpo.GenerateReportToFile([Microsoft.GroupPolicy.ReportType]::Html, $(Join-Path $workspace "$($gpo.DisplayName).html")) if ($gpo.DisplayName+"_new.xml" -notin $errors.fichier) { try { Write-Debug "Création d'un rapport XML pour $($gpo.displayname) nommé $($gpo.displayname)_new.xml" $gpo.GenerateReportToFile([Microsoft.GroupPolicy.ReportType]::Xml, $(Join-Path $workspace "$($gpo.DisplayName)_new.xml")) } catch { Write-Debug "Erreur lors de l'export de la GPO" $errors.Add([pscustomobject]@{ "Fichier" = $gpo.displayName "Type" = $Error[0].CategoryInfo.Reason "Action" = "Export de la GPO" "Erreur" = "Une erreur s'est produite lors de l'export de la GPO. $($error[0].Exception.Message)" }) } } else { Write-Debug "Le déplacement du fichier source $($gpo.displayname) est en erreur. Pas d'export fait" $errors.Add([pscustomobject]@{ "Fichier" = $gpo.displayName "Type" = "Manuel" "Action" = "Export de la GPO" "Erreur" = "Un déplacement du fichier source est en erreur. Cette GPO ne sera pas exportée pour préserver l'historique." }) } #On vérifie si le répertoire de backup existe ou non if (!(Test-Path $(Join-Path $workspace $gpo.DisplayName))) { Try { New-Item -ItemType Directory -Name $gpo.DisplayName -ErrorAction Stop -Verbose -Force Backup-GPO -Guid $gpo.id -Path $(Join-Path $workspace $gpo.DisplayName) -Comment "Backup du $date" -ErrorAction Stop -Verbose } catch { $errors.Add([PSCUSTOMOBJECT]@{ "Fichier" = $gpo.DisplayName "Type" = $Error[0].CategoryInfo.Reason "Action" = "Création de backup" "Erreur" = "Une erreur s'est produite lors de la création du répertoire de backup ou du backup pour la GPO. $($Error[0].Exception.Message)" }) } } } #Une feuille CSS qui sera intégrée au fichier HTML afin de pouvoir avoir un affichage un peu plus sympa $css = @" "@ try { $htmlfile = new-object System.Collections.Generic.List[string] } catch { try { $htmlfile = [System.Collections.Generic.List[string]]::new() } catch { exit 50 } } $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add('') $htmlfile.Add($css) $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add("") Write-Debug "Parcours des nouveaux fichiers extraits de GPOS" foreach ($newitem in (Get-ChildItem $workspace -Filter "*_new.xml")) { $isnewgpo = $false try { Write-Debug "Récupération de $($newitem.name.replace(`"_new.xml`","_$date.xml`"))" $olditem = Get-Item $newitem.name.replace("_new.xml","_$date.xml") -ErrorAction Stop } catch { Write-Debug "Pas de fichier en date du jour. Recherche d'un plus ancien" $foundfiles = Get-ChildItem | Where-Object {$_.name -match "^$($newitem.Name.replace("_new.xml","_\d{$($date.length)}.xml$"))"} #if ((get-childitem $newitem.Name.Replace("_new.xml","_*.xml") -Exclude $newitem.Name).count -ne 0) if ($foundfiles.count -ne 0) { try { Write-Debug "Récupération du fichier le plus récent qui n'est pas $($newitem.name)" $lastknownitem = (get-childitem -Exclude $newitem.Name -ErrorAction Stop | Where-object {$_.name -match "^$($newitem.Name.replace("_new.xml","_\d{$($date.length)}.xml$"))"}) | Sort-Object -Property name -Descending | Select-Object -First 1 Write-Debug "Fichier trouvé : $($lastknownitem.name)" $olditem = $lastknownitem } catch { Write-Debug "Aucun fichier plus récent. La GPO est nouvelle." $isnewgpo = $true } if ($isnewgpo -eq $true -or $olditem -eq $null) { $errors.Add([pscustomobject]@{ "Fichier" = $olditem.Name "Type" = $Error[0].CategoryInfo.Reason "Action" = "Récupération de l'ancien fichier $($newitem.name.replace("_new.xml","_$date.xml"))" "Erreur" = "La récupération de l'ancien fichier n'a pas pu être effectuée. $($Error[0].Exception.Message). La gpo est nouvelle" }) $isnewgpo = $true } } else { Write-Debug "Aucun fichier plus ancien trouvé." $isnewgpo = $true } } #récupération des fichiers Write-Debug "Lecture du contenu de $($newitem.name) pour création d'un objet XML" [xml]$xmlnew = Get-Content $newitem if ($isnewgpo -eq $true) { Write-Debug "La gpo $($newitem.name) est nouvelle, on passe ce fichier" $errors.Add([pscustomobject]@{ "Fichier" = $newitem.Name "Type" = "Nouvelle GPO" "Action" = "La GPO $($newitem.name.replace("_new.xml","_$date.xml")) a été créée en date du $($xmlnew.GPO.CreatedTime)" "Erreur" = "GPO Non traitée car nouvelle. $($Error[0].Exception.Message)." }) continue } Write-Debug "Lecture du contenu de $($olditem.name) pour création d'un object XML de comparaison" [xml]$xmlold = Get-Content $olditem if ($xmlold.GPO.ModifiedTime -ne $xmlnew.GPO.ModifiedTime) { Write-Debug "Les fichiers comportent des dates de modifications différentes." $sendmail = $true Write-Host -ForegroundColor Green "Modifications détectées entre $($newitem.name) et $($olditem.name)!" $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add("") Write-Debug "Selection de la GPO pour sauvegarde" $thisgpo = $gpos | Where-Object {$_.DisplayName -eq $xmlnew.GPO.Name} try { Backup-GPO -Guid $thisgpo.id -Path $(Join-Path $workspace $thisgpo.DisplayName) -Comment "Backup du $date" -ErrorAction Stop } catch { $errors.Add([pscustomobject]@{ "Fichier" = $thisgpo.DisplayName "Type" = "BackupGPO" "Action" = "La GPO $($thisgpo.DisplayName) n'a pas pu être sauvegardée" "Erreur" = "GPO Non sauvegardée. $($Error[0].Exception.Message)." }) } #$enc sera utilisé pour lire et convertir le fichier xml $enc = [System.Text.Encoding]::$encoding $xmlold.InnerXml.replace("><",">`n<")| Out-File $(join-path $workspace "__xmlold.xml") -Encoding $encoding -Force $xmlnew.InnerXml.replace("><",">`n<")| Out-File $(join-path $workspace "__xmlnew.xml") -Encoding $encoding -Force Write-Debug "Analyse des fichiers XML et comparaison via l'outil diff_match_patch" $differences = Highlight-Differences -originalfile (Get-Item .\__xmlold.xml) -differences (Get-Item .\__xmlnew.xml) if ($differences) { $htmlfile.Add("") $htmlfile.Add("") } else { #Supprimer le fichier XML fraichement crée, inutile car aucune modification par rapport à l'ancien Remove-Item $newitem -Verbose -Force } } $htmlfile.Add("
GPODate de modificationDifférences
$($xmlold.GPO.Name)$($xmlold.GPO.ModifiedTime)") foreach ($difference in $differences) { $htmlfile.add("$($difference.replace('','
').replace('','
'))") } } else { #Encore valide? Write-Debug "une erreur s'est produite lors de la comparaison des fichiers." $htmlfile.Add("
") $htmlfile.Add("ERREUR LORS DE LA COMPARAISON DES FICHIERS") } $htmlfile.Add("
") $htmlfile.Add("
") $htmlfile.Add("
") $htmlfile.Add("
") if ($infos.Count -gt 0) { $sendmail = $true $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add("") $htmlfile.Add("") foreach ($entry in $infos) { $htmlfile.Add("") $htmlfile.Add("") if ($entry.Information -eq "La GPO a été créée") { $htmlfile.Add("") } else { $htmlfile.Add("") } $htmlfile.Add("") } } $htmlfile.Add("
GPOAction
$($entry.gpo)Creation d'une GPOSuppression d'une GPO
") $htmlfile.Add("") $htmlfile.Add("") Write-Debug "Export du fichier HTML $(Join-Path $workspace "$htmlfilename")" $htmlfile | Out-File $(Join-Path $workspace "$htmlfilename") $htmlcontent = "" foreach ($ligne in $htmlfile) { $htmlcontent += $ligne} $accesserror = $false if ($sendmail) { Send-MailMessage -From "$from" -To $to -Subject "Modifications sur GPO" -Body $htmlcontent -BodyAsHtml -Attachments $(Join-Path $workspace "$htmlfilename") -SmtpServer "$mailserver" -Port $smtpport -UseSsl:$usessl -Encoding $encoding } $creds = new-object System.Management.Automation.PSCredential -ArgumentList "$username",$("$password" | ConvertTo-SecureString -AsPlainText -Force) try { $drive = New-PSDrive -PSProvider FileSystem -Name "strategies" -Root "$backuplocation" -Credential $creds -ErrorAction Stop } catch { $accesserror = $true Write-Host -ForegroundColor Red "Impossible de se connecter au lecteur réseau. $($error[0].exception.message)" } if (!$accesserror) { foreach ($file in (Get-ChildItem $workspace "*.html" |Where-Object {$_.name -ne "$htmlfilename"})) { try { Move-Item $file "strategies:\" -Force -ErrorAction Stop -Verbose #Remove-Item $file -Force -ErrorAction Stop -Verbose } catch { Write-Host "Erreur sur $($file.name) : $($error[0].exception.Message)" } } } remove-psdrive -Name "strategies" -PSProvider FileSystem