Blog/Active Directory/gpo_versionning.ps1

564 lines
19 KiB
PowerShell
Raw Permalink Normal View History

2023-12-17 19:52:00 +01:00
#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 "*<del style=*" -or $_ -like "*<ins style=*" -or $_ -like "*</del>*" -or $_ -like "*</ins>*")
{
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 "*<del style=*" -or $textdiffs[$end] -like "*<ins style=*" -or $textdiffs[$end] -like "*</del>*" -or $textdiffs[$end] -like "*</ins>*")
{
$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])<br>")
}
$null = $temparray.Add("******************************************************<br>")
}
}
$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 = @"
<style media="screen" type="text/css">
table {
width: 100%;
border: 2px solid;
border-collapse: collapse;
}
th, td, tr {
border: 1px solid;
}
th {
color: black;
text-align: center;
vertical-align: middle;
height: 50px;
font-size: 30px;
background-color: lightblue;
}
td[div=gpo]{
font-size: 18px;
vertical-align: top;
text-align: center;
text-transform: uppercase;
font-weight: bold;
}
td[div=time]{
vertical-align: top;
text-align: center;
}
p {
font-family: verdana;
font-size: 20px;
}
div {
font-family: Arial;
}
div.delete {
background-color: lightcoral;
color: darkred;
text-decoration:line-through;
display: block;
font-weight: bold;
font-family: verdana;
}
div.create{
background-color: lightgreen;
color: darkgreen;
font-style: italic;
font-family: verdana;
}
div.warning{
background-color: yellow;
font-weight: bold;
font-size: 150%;
}
</style>
"@
try
{
$htmlfile = new-object System.Collections.Generic.List[string]
}
catch
{
try
{
$htmlfile = [System.Collections.Generic.List[string]]::new()
}
catch
{
exit 50
}
}
$htmlfile.Add("<html>")
$htmlfile.Add("<head>")
$htmlfile.Add('<meta charset="utf-8" />')
$htmlfile.Add($css)
$htmlfile.Add("</head>")
$htmlfile.Add("<body>")
$htmlfile.Add("<table>")
$htmlfile.Add("<tr>")
$htmlfile.Add("<th>GPO</th>")
$htmlfile.Add("<th>Date de modification</th>")
$htmlfile.Add("<th>Différences</th>")
$htmlfile.Add("</tr>")
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("<tr>")
$htmlfile.Add("<td div='gpo'>$($xmlold.GPO.Name)</td>")
$htmlfile.Add("<td div='time'>$($xmlold.GPO.ModifiedTime)</td>")
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("<td>")
foreach ($difference in $differences)
{
$htmlfile.add("$($difference.replace('<span>','<div>').replace('</span>','</div>'))")
}
}
else
{
#Encore valide?
Write-Debug "une erreur s'est produite lors de la comparaison des fichiers."
$htmlfile.Add("<td>")
$htmlfile.Add("ERREUR LORS DE LA COMPARAISON DES FICHIERS")
}
$htmlfile.Add("</td>")
$htmlfile.Add("</tr>")
}
else
{
#Supprimer le fichier XML fraichement crée, inutile car aucune modification par rapport à l'ancien
Remove-Item $newitem -Verbose -Force
}
}
$htmlfile.Add("</table>")
$htmlfile.Add("<br>")
$htmlfile.Add("<br>")
$htmlfile.Add("<br>")
if ($infos.Count -gt 0)
{
$sendmail = $true
$htmlfile.Add("<table>")
$htmlfile.Add("<tr>")
$htmlfile.Add("<th>GPO</th>")
$htmlfile.Add("<th>Action</th>")
$htmlfile.Add("</tr>")
foreach ($entry in $infos)
{
$htmlfile.Add("<tr>")
$htmlfile.Add("<td>$($entry.gpo)</td>")
if ($entry.Information -eq "La GPO a été créée")
{
$htmlfile.Add("<td div='create'>Creation d'une GPO</td>")
}
else
{
$htmlfile.Add("<td div='delete'>Suppression d'une GPO</td>")
}
$htmlfile.Add("</tr>")
}
}
$htmlfile.Add("</table>")
$htmlfile.Add("</body>")
$htmlfile.Add("</html>")
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