Le blog technique

Toutes les astuces #tech des collaborateurs de PI Services.

#openblogPI

Retrouvez les articles à la une

powershell rapport-gpo

SCRIPT POWERSHELL RAPPORT GPO

Dans le cadre de la refonte et de l’audite des GPO chez un client, j’ai créé un script PowerShell dans le but de faire une remonter avec rapport html sur toutes les GPO’s du domaine puis envoie de celui-ci  dans le corps du mail.
Voici les caractéristiques du script :
-    Un script qui fait apparaitre toutes les GPO’s du domaine avec leur liaisons sur les Site, Domain et Ou
-    Les GPO’s modifier de moins de 7 jours doivent apparaitre en rouge
-    Rapport GPO sous forme de tableau et envoyer par mail
-    Copie du rapport GPO dans un répertoire du serveur (suppression automatique des rapports de plus de 30 jours

 

Tout d’abord il mettre un titre au script, il faut savoir que dans un script PowerShell tout ligne commençant par # veut dire que c’est un commentaire, PowerShell n’exécute pas ce qui suit mais cela est intéressant du point de vue afficher un commentaire

#/////////////////////////////////////////////////////////////////////////////
#//
#// Titre: GPO-Report.ps1
#// Auteur: OUASTI Khatir
#// Version: 1.0
#//
#/////////////////////////////////////////////////////////////////////////////

L’importation des modules suivant est important car il permet d’exécuter le script sans blocage (niveau droit d’exécuter le script) Et surtout charge les modules active directory et grouppolicy qui vont permettent l’exécution des commandes liés à l’AD ou au GPO
############import des modules AD et grouppolicy##########

Set-ExecutionPolicy -ExecutionPolicy Bypass
Import-Module activedirectory
Import-Module grouppolicy

 

 

Ci-dessous La construction du tableau HTML a été choisi du fait quel permet d’être intégré au corps du mail à savoir que certaines dates apparaissent en rouge c’est la solution la mieux adapter. (L’autre solution corps du mail avec fichier texte pas de couleur pas adapté).
#######construction du style et du tableau html##############

$style = @’
<style>
H3 {text-align: center;background-color: lightgreen;}
BODY{background-color:lightblue;}
TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH{border-width: 1px;padding: 2px;border-style: solid;border-color: black;background-color:thistle}
TD{border-width: 1px;padding: 2px;border-style: solid;border-color: black}
</style>
‘@

###les variables dates ci-dessous sont associés au nom du rapport #####

$datereport = (get-date).AddDays(0).ToString("dd-MM-yyyy")
$thedate = (get-date).AddDays(0).ToString(‘dd/MM/yyyy HH:mm:ss’)
$resultdate = (get-date).AddDays(-7)

La commande ci-dessous permet d’afficher toutes les GPO du domaine, sort-Object permet de classer la colonne modification par ordre de date la plus récente, select-Object affiche les colonnes que l’on a choisi.

####Extraction de l’information concernant toutes les GPO’s #####

$GPO = Get-GPO -all | Sort-Object -descending modificationtime | Select-Object displayname, id, owner, creationtime ,modificationtime

 

La commande ci-dessous permet d’afficher les noms des SITES, DOMAINES ou Unités Organisationnelles auxquelles sont liées des GPO’s, il est clair que pour faire apparaitre les liaisons des GPO’s celle-ci n’est pas contenu dans la commande GET-GPO, il fallut extraire l’information à partir de la commande GET-GPOREPORT.

##########Extraction de l’information en fonction du nom de la GPO lié à une ou plusieurs OU######################

$GPOReport = Get-GPOReport -all -ReportType xml | % {
        ([Xml]$_).gpo | select name,@{n="SOMName";e={$_.LinksTo | % {$_.SOMName}}},@{n="SOMPath";e={$_.LinksTo | %{$_.SOMPath}}}}

 

Les variables $body nous permet la construction des colonnes du tableau HTML et aussi des titres du rapport. 

#############construction des colonnes du tableau html#############

$Body = "<h3>Rapport GPO journalier: $thedate</h3><br><table><tr><th>DISPLAYNAME</th><th>ID</th><th>OWNER</th><th>CREATIONTIME</th><th>MODIFICATIONTIME</th><th>LINK-OU-SITE-DOMAIN</th></tr>"

La construction de la boucle va permettre d’aller chercher chaque valeur date de la colonne modificationtime si la GPO a été modifié il y’a moins de 7 jours la date apparaitra en fond rouge sur écriture blanc.

###########construction de la boucle pour l’application de la couleur rouge pour la colonne modificationtime#######

Foreach($GPOALLDOMAIN in $GPO) {

$displayname = $GPOALLDOMAIN.displayname
$ID = $GPOALLDOMAIN.id
$owner = $GPOALLDOMAIN.owner
$creationtime = $GPOALLDOMAIN.creationtime
$modificationtime = $GPOALLDOMAIN.modificationtime

        $Body +=  "<tr><td>$displayname</td><td>$ID</td><td>$owner</td><td>$creationtime</td>"

######Application changement de couleur Rouge sur chaque cellule de la colonne modificationtime si une valeur date est supérieure à $resultdate################

    If ($GPOALLDOMAIN.modificationtime -gt $resultdate) {

        $Body += "<td bgcolor=`"`#FF0000`"><font color=`"`#FFFFFF`">$modificationtime</font></td>"

La partie du script ci-dessous permet de joindre au tableau HTML, les valeurs obtenu par les commandes GET-GPOReport et GET-GPO.

##Application d’aucune changement sur la colonne modificationtime #####

      } else {

        $Body += "<td>$modificationtime</td>"
      
     
    }

    $Result = $GPOReport | Where-Object {$_.name -eq $displayname}
    $ResultString = $Result.SOMPath -join ";"

#######comparaison entre le nom de la gpo de la commande get-gpo et get-GPOreport et ajoute le résultat $resultstring à la colonne "link" ##########

    if ($ResultString -eq "") {$Body += "<td align=center bgcolor=`"`#CCFF00`">NO-LINK</td>"}
    $Body += "<td>$ResultString</td>"
    $Body +=  "</tr>"
}
#############fin de la boucle du tableau html################

$Body += "</table>"

##############Conversion du tableau en html###################

ConvertTo-HTML -head $style -body $Body | set-content "D:\Scripts\GPO-RAPPORT\LOGS\ReportGPO-$datereport.htm"

 

Bien évidemment, il faut penser à effectuer un nettoyage automatique des rapports qui ont plus d’un mois.

##########suppressions des fichiers de plus de 30 jours #############

dir \\serveur1\d$\Scripts\GPO-RAPPORT\LOGS -recurse | where { ((get-date)-$_.lastwritetime).days -gt 30 } | Remove-Item –Force

 

 

L’envoie du rapport dans le corps du message, je l’ai effectué en version 1.0 pour plus de clarté.

###########information d’identification pour l’envoie du message et Rapport HTML intégrer au corps du message adapter pour PowerShell v1.0########

$fromaddress = "email de l’expéditeur"
$toaddress = "email du 1er destinataire"
$bccaddress = "email du 2 eme destinataire"
$CCaddress = "email du 3 eme destinataire"
$Subject = "Envoie Rapport GPO journalier (Copie dans \\serveur1\Scripts\GPO-RAPPORT\LOGS)"
$body = get-content "D:\Scripts\GPO-RAPPORT\LOGS\ReportGPO-$datereport.htm"
#$Attachment = "D:\Scripts\GPO-RAPPORT\test.txt" (non utilisé pour mon script mais pourrais servir pour joindre un fichier au mail)
$smtpserver = "smtp.domaine.com"

##########information d’authentification pour l’envoie du message##########################

$message = new-object System.Net.Mail.MailMessage
$message.From = $fromaddress
$message.To.Add($toaddress)
$message.CC.Add($CCaddress)
$message.Bcc.Add($bccaddress)
$message.IsBodyHtml = $True
$message.Subject = $Subject

# création du nouvel objet Net.Mail.Attachment($attachment) permettant #d’attacher le fichier en question.
#$attach = new-object Net.Mail.Attachment($attachment)
#$message.Attachments.Add($attach)

$message.body = $body
$CredUser = "user1"
$CredPassword = "Password"
$smtp = new-object Net.Mail.SmtpClient($smtpserver, 25)
$SMTP.EnableSsl = $true
$SMTP.Credentials = New-Object System.Net.NetworkCredential($CredUser, $CredPassword);
$smtp.Send($message)

#######################################################
 
 
SCRIPT COMPLET

#/////////////////////////////////////////////////////////////////////////////
#//
#// Titre: GPO-Report.ps1
#// Auteur: OUASTI Khatir
#// Version: 1.0
#//
#/////////////////////////////////////////////////////////////////////////////

 

#####import des modules AD et grouppolicy########################
Set-ExecutionPolicy -ExecutionPolicy Bypass
Import-Module activedirectory
Import-Module grouppolicy
###############construction du style et du tableau html##############

$style = @’
<style>
H3 {text-align: center;background-color: lightgreen;}
BODY{background-color:lightblue;}
TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH{border-width: 1px;padding: 2px;border-style: solid;border-color: black;background-color:thistle}
TD{border-width: 1px;padding: 2px;border-style: solid;border-color: black}
</style>
‘@
$datereport = (get-date).AddDays(0).ToString("dd-MM-yyyy")
$thedate = (get-date).AddDays(0).ToString(‘dd/MM/yyyy HH:mm:ss’)
$resultdate = (get-date).AddDays(-7)

###Extraction de l’information concernant tout les GPO du domaine ####

 

$GPO = Get-GPO -all | Sort-Object -descending modificationtime | Select-Object displayname, id, owner, creationtime ,modificationtime

###Extraction de l’information en fonction du nom de la GPO lié à une ou plusieurs OU #####################

$GPOReport = Get-GPOReport -all -ReportType xml | % {
        ($_).gpo | select name,@{n="SOMName";e={$_.LinksTo | % {$_.SOMName}}},@{n="SOMPath";e={$_.LinksTo | %{$_.SOMPath}}}}

#######construction du style et du tableau html################

$Body = "<h3>Rapport GPO journalier: $thedate</h3><br><table><tr><th>DISPLAYNAME</th><th>ID</th><th>OWNER</th><th>CREATIONTIME</th><th>MODIFICATIONTIME</th><th>LINK-OU-SITE-DOMAIN</th></tr>"

########construction de la boucle pour l’application de la couleur rouge pour la colonne modificationtime##############################
Foreach($GPOALLDOMAIN in $GPO) {

$displayname = $GPOALLDOMAIN.displayname
$ID = $GPOALLDOMAIN.id
$owner = $GPOALLDOMAIN.owner
$creationtime = $GPOALLDOMAIN.creationtime
$modificationtime = $GPOALLDOMAIN.modificationtime

        $Body +=  "<tr><td>$displayname</td><td>$ID</td><td>$owner</td><td>$creationtime</td>"

##########Application changement de couleur Rouge sur chaque cellule de la colonne modificationtime si une valeur date est supérieure à $resultdate################

    if ($GPOALLDOMAIN.modificationtime -gt $resultdate) {

        $Body += "<td bgcolor=`"`#FF0000`"><font color=`"`#FFFFFF`">$modificationtime</font></td>"

##########Application d’aucune changement sur la colonne modificationtime #########################################

      }else {

        $Body += "<td>$modificationtime</td>"
      
     
    }

    $Result = $GPOReport | Where-Object {$_.name -eq $displayname}
    $ResultString = $Result.SOMPath -join ";"

##########comparaison entre le nom de la gpo de la commande get-gpo et get-GPOreport et ajoute le résultat $resultstring à la colonne "link" ################
    if ($ResultString -eq "") {$Body += "<td align=center bgcolor=`"`#CCFF00`">NO-LINK</td>"}
    $Body += "<td>$ResultString</td>"
    $Body +=  "</tr>"
}
########fin de la boucle du tableau html##################

$Body += "</table>"

#########Conversion du tableau en html#####################

ConvertTo-HTML -head $style -body $Body | set-content "D:\Scripts\GPO-RAPPORT\LOGS\ReportGPO-$datereport.htm"

##########suppressions des fichiers de plus de 30 jours #############

dir \\serveur1\d$\Scripts\GPO-RAPPORT\LOGS -recurse | where { ((get-date)-$_.lastwritetime).days -gt 30 } | Remove-Item -Force

###########information d’identification pour l’envoie du message et Rapport HTML intégrer au corps des messages adapter pour powershell v1.0########

$fromaddress = "email de l’expéditeur"
$toaddress = "email du 1er destinataire"
$bccaddress = "email du 2 eme destinataire"
$CCaddress = "email du 3 eme destinataire"
$Subject = "Envoie Rapport GPO journalier (Copie dans \\serveur1\Scripts\GPO-RAPPORT\LOGS)"
$body = get-content "D:\Scripts\GPO-RAPPORT\LOGS\ReportGPO-$datereport.htm"
#$Attachment = "D:\Scripts\GPO-RAPPORT\test.txt" (non utilisé pour mon script mais pourrais servir pour joindre un fichier au mail)
$smtpserver = "smtp.domaine.com"

##information d’authentification pour l’envoie du message########

$message = new-object System.Net.Mail.MailMessage
$message.From = $fromaddress
$message.To.Add($toaddress)
$message.CC.Add($CCaddress)
$message.Bcc.Add($bccaddress)
$message.IsBodyHtml = $True
$message.Subject = $Subject

# création du nouvel objet Net.Mail.Attachment($attachment) permettant d’attacher le fichier en question.
#$attach = new-object Net.Mail.Attachment($attachment)
#$message.Attachments.Add($attach)

$message.body = $body
$CredUser = "user1"
$CredPassword = "Password"
$smtp = new-object Net.Mail.SmtpClient($smtpserver, 25)
$SMTP.EnableSsl = $true
$SMTP.Credentials = New-Object System.Net.NetworkCredential($CredUser, $CredPassword);
$smtp.Send($message)

#################################################################################

Exchange : Découverte de l’agent de scripting

Introduction

Depuis Exchange 2010, il existe les agents d’extension des Cmdlets. C’est une fonctionalité méconnue (car peu documentée) et pourtant très utile dans beaucoup d’entreprise.

Ces agents permettent d’étendre les fonctionnalités de base d’Exchange. Par exemple lors de la création d’une boîte aux lettres, si une base de données n’est pas renseignée alors un agent se charge d’en choisir une automatiquement. Quelque soit le mode d’administration (Powershell ou via la console graphique EMC), l’agent se lancera. Cela vient entre autre du fait que la console Exchange ne fait qu’exécuter du Powershell en tâche de fond. Il est possible d’obtenir la liste de ces agents en exécutant la Cmdlet suivante dans une session Powershell Exchange :

Voici le résultat obtenu sur une infrastructure sans paramétrage particulier de ces agents :

Exchange Extension Agent Listing

Il existe de nombreux agents ; notamment pour la gestion de l’OAB ou des boîtes aux lettres. Dans le résultat obtenu, 2 attributs vont nous intéresser : l’activation et la priorité. Le premier permet simplement de savoir si l’agent est actuellement utilisé ou non. Le second concerne l’ordre d’application. En effet, plusieurs agents peuvent agir sur la même chose (par exemple : le choix de la base de données pour une boîte aux lettres). La priorité permet de définir l’agent qui sera utilisé (celui qui a la priorité la plus basse, les autres ne seront pas utilisés). Pour changer la priorité d’un agent, il suffit d’utiliser la commande suivante :

L’agent qui nous intéresse dans cet article est le "Scripting Agent". Nous allons voir comment l’utiliser ainsi que quels exemples d’utilisation.

L’agent de script

A contrario des autres agents, l’agent de scripting est entièrement customisable par les administrateurs Exchange. Typiquement, il va nous permettre par exemple, de réaliser certaines actions au moment de la création d’une boîte aux lettres et donc de l’exécution de la commande New-Mailbox ou Enable-Mailbox (activer/désactiver POP3/Single item recovery etc, création d’une boîte d’archive, envoi d’un mail automatique à l’utilisateur). On peux aussi imaginer un export automatique de la boîte aux lettres au format PST lorsque la commande remove-mailbox sera lancée. D’autres types d’actions sont réalisables. Elles seront détaillées plus loin dans cet article. Tout type de script Powershell peux être intégré.

Par défaut l’agent de scripting n’est pas activé. C’est pourquoi, on utilise la commande :

Cette commande active l’agent de scripting sur tous les serveurs Exchange de l’organisation.

Attention : Le fichier de configuration doit être présent sur tous vos serveurs Exchange. En effet, si l’agent de scripting est activé et que l’un des serveurs ne possède pas le fichier alors des erreurs peuvent survenir lorsque l’on appelle une commande Powershell ou lorsqu’on lance la console Exchange (impossibilité de se connecter au serveur ne possédant pas le fichier de configuration).

L’agent de scripting ne possède aucune configuration par défaut. Il convient aux administrateurs Exchange de la créer. Celle-ci se fait au travers du fichier ScriptingAgentConfig.xml qui doit être positionné dans le dossier C:\Program Files\Microsoft\Exchange Server\V14\Bin\CmdletExtensionAgents (à moduler suivant le répertoire d’installation d’Exchange). Un exemple existe dans ce même répertoire nommé ScriptingAgentConfig.xml.sample).

Ce fichier contiendra tous nos scripts d’automatisation. Regardons maintenant la hiérarchie de ce fichier XML :

La balise Configuration contient l’ensemble des scripts qui seront utilisés par l’agent de scripting. C’est la balise racine.

Les balises Feature contiennent chaque fonctionnalité que l’on souhaite ajouter. Il peut y en envoir plusieurs au sein d’une balise Configuration. Elle possède chacune 2 attributs :

  • Name : pour le nom que l’on souhaite donner à notre fonctionnalité)
  • Cmdlets : permet de spécifier les Cmdlets Powershell Exchange qui vont déclencher la fonctionnalité. S’il y en a plusieurs, elles doivent être séparées par des virgules (Exemple : "New-Mailbox,Enable-Mailbox").

La balise API Call précice à quel moment la fonctionnalité se déclenche. Elle contient aussi le script qui sera lancé au déclenchement. Il peux y en envoir plusieurs au sein d’une balise Feature. Elle possède un attribut Name qui peut avoir 4 valeurs possibles :

  • OnComplete : Le script fourni sera exécuté lorsque la commande appelé aura déjà été exécuté. Exemple : Après la création d’une boîte aux lettres, on souhaite envoyer un mail de bienvenu à l’utilisateur et activer le Single Item Recovery.
  • Validate : Utile lorsque l’on souhaite valider des attributs. Le script se déclenchera avant l’exécution de la commande. Exemple : On souhaite être sur que les attributs Location et Phone ont été renseignés ou qu’ils respectent une certaine nomenclature pendant la création d’une boîte aux lettres. Ainsi l’administrateur recevra une erreur lors de l’exécution de la commande comme si ces attributs étaient obligatoire. Lorsque le retour est $null alors l’étape de validation est un succès.
  • ProvisionDefaultProperties : Cela permet de définir des valeurs par défaut pour les propriétés d’un objet. Exemple : Lorsque l’on crée une boîte aux lettres Exchange, on peux imaginer une règle qui choisit automatiquement la base de données en fonction de la première lettre du nom de la personne. Attention, dans cet exemple, il est nécessaire de désactiver l’agent Mailbox Resources Management ou de baisser sa priorité en dessous de celle de l’agent de scripting. En effet, l’agent Mailbox Resources Management est en charge de l’attribution automatique d’une base de données si aucune n’est renseignée.
  • UpdateAffectedIConfigurable : Cette API offre la possibilité de définir des propriétés juste avant l’opération de validation.

L’ordre d’exécution des différentes API lorsque l’on exécute une commande Exchange est le suivant  :

ProvisionDefaultProperties – UpdateAffectedIConfigurable – Validate – OnComplete

L’exécution de la commande Powershell Exchange a lieu entre les étapes Validate et OnComplete.

Enfin la balise Common permet de définir des fonctions Powershell pouvant être utilisées dans les scripts des balises ApiCall (A utiliser comme une librairie). On peut aussi charger ses propres scripts Powershell.

La mise en forme du fichier ScriptingAgentConfig.xml est importante. En effet, il apparait que lorsque des espaces inutiles sont présents, Exchange génère une erreur similaire à celle ci-dessous :

ScriptingAgent Error

De plus, un événement est généré :

ScriptingAgent Error Event 

Pour ma part, afin d’éviter tout problème, je me suis rendu compte qu’il ne fallait mieux pas commenter les scripts présents dans le fichier xml.

NB : Une fois l’agent de scripting activé, les modifications du fichier ScriptingAgentConfig.xml sont prises en compte automatiquement.

Exemple d’utilisation avec l’événement OnComplete :

Lorsqu’on utilise l’API OnComplete, la variable $succeeded existe si la commande a réussi. Cela permet de gérer les cas d’échecs (il serait impossible d’effectuer un traitement sur une boîte aux lettres qui n’existerait pas).

L’exemple ci-dessous est un fichier ScriptingAgentConfig.xml permettant d’activer la boîte d’archive et le single item recovery lorsqu’une nouvelle boîte aux lettres Exchange est créée (Commande New-Mailbox et Enable-Mailbox). On remarque que l’on accède aux paramètres définis par l’utilisateur via la variable $provisioningHandler qui contient un hastable nommé UserSpecifiedParameters.

Exemple d’utilisation avec l’événement Validate : 

Ce nouvel exemple montre cette fois-ci l’usage de l’API Validate. Ici, lorsqu’une boîte aux lettres de salle est créée, on vérifie que son nom est bien du type : Salle, XX où XX est un nombre. Si le test échoue alors une erreur est retourné avec un message qui sera affiché pour l’administrateur (que l’action soit réalisée via EMS ou l’EMC).

SCOM – The RPC server is unavailable.(0x800706BA)

Le fonctionnement normal du Health Service SCOM (qu’il se trouve sur un Management server ou un agent supervisé) implique qu’il essaye régulièrement de vérifier la validité des RunAs Accounts (à chaque démarrage du service, à chaque changement de configuration, à réception d’un nouveau Management Pack etc).

Il peut arriver que cette vérification échoue, provocant alors une alerte de type The Health Service cannot verify the future validity of the RunAs account. (évènement 7016 dans le journal Operations Manager)

Cette alerte peut avoir de nombreuses origines, et le message qui l’accompagne peut normalement aider à identifier la cause.

Intéressons-nous ici plus spécifiquement à l’évènement “The RPC server is unavailable.(0x800706BA)”, observé chez un client qui dispose d’une forêt multi-domaines. Certains serveurs supervisés se trouvent dans un domaine différent de celui où se trouve le compte SCOM Action.

Ce message laisse supposer des difficultés pour joindre un contrôleur de domaine capable d’authentifier le compte.

Commençons donc par identifier quel est le DC qui est contacté, en exécutant la commande nltest /dsgetdc:corp.domaine.lan sur le serveur d’où l’erreur provient (et où corp.domaine.lan correspond au domaine qui héberge le RunAs account problématique) :

image

Le champ DC indique quel est ce contrôleur de domaine.

Il faut s’assurer qu’il est joignable sur les ports 88 (kerberos) et 389 (LDAP), et ce à la fois en passant par son FQDN et par son nom court

C’était ici la cause de notre problème : la résolution DNS ne permettait pas d’interroger ce DC via son nom court. Un simple ajout de suffixes DNS dans la configuration de la carte réseau a donc permis de corriger l’erreur.

Astuce supplémentaire : afin de forcer une revérification des credentials par le Health Service, il suffit de redémarrer ce dernier.