Aller au contenu principal

Automatiser : PowerShell et scripts Linux

Ce que ce chapitre apporte

  • Expliquer ce que l'automatisation apporte au-delà du gain de temps.
  • Lire un script PowerShell : cmdlets, pipeline, variables, conditions, boucles.
  • Reconnaître les cmdlets d'administration de l'annuaire.
  • Automatiser la création de comptes à partir d'un fichier, avec règle de nommage.
  • Écrire un script de maintenance Linux et le planifier.
  • Rendre un script sûr : mode simulation, journalisation, idempotence.
« Plusieurs techniciens interviennent quotidiennement sur le parc. Chacun applique ses propres habitudes. » C'est la dernière plainte d'Ardexia, et c'est celle qui explique toutes les autres : sans procédure exécutable, la procédure n'existe pas. Ce chapitre traite de l'automatisation, non pas comme un gain de temps, mais comme le seul moyen de rendre une opération reproductible. On y voit PowerShell côté Windows, bash et cron côté Linux, et surtout ce qu'un script doit garantir pour être digne de confiance.

Pourquoi automatiser

Définition

L'automatisation est l'exécution d'une tâche sans intervention humaine permanente. Un script en est le support : une suite d'instructions exécutées par un interpréteur.

Quatre raisons sont couramment avancées. Elles ne se valent pas.

RaisonPortée
Gagner du tempsréelle, mais la moins importante
Réduire les erreursun script ne se trompe pas de case à cocher
Standardiser les opérationsla raison décisive chez Ardexia
Simplifier les tâches répétitivesconséquence des précédentes
Le vrai apport est la reproductibilité
Une procédure écrite dans un document dérive : chacun l'interprète, l'adapte, en saute une étape un jour de rush. Une procédure écrite dans un script s'exécute à l'identique, par tout le monde, y compris à trois heures du matin.
Et elle se relit. « Comment crée-t-on un compte ici ? » a alors une réponse unique et vérifiable : le script.

PowerShell, les bases

Définition

PowerShell est le langage de scripting et d'administration de Microsoft. Sa particularité tient en un point : il manipule des objets, pas du texte.

Une commande PowerShell s'appelle une cmdlet et suit toujours la forme Verbe-Nom : Get-Service, New-ADUser, Set-Content. La convention rend le vocabulaire devinable.

# Une variable commence par $
$Nom = "Dupont"
$Age = 22

# Une condition. Les operateurs sont textuels : -gt, -lt, -eq, -ne
if ($Age -gt 18) {
    Write-Host "Majeur"
} else {
    Write-Host "Mineur"
}

# Une boucle sur une collection
$Utilisateurs = @("marie", "marc", "melanie")
foreach ($User in $Utilisateurs) {
    Write-Host "Traitement de $User"
}

# Une fonction
function Get-EspaceLibre {
    param([string]$Lecteur = "C")
    $Disque = Get-PSDrive $Lecteur
    return [math]::Round($Disque.Free / 1GB, 2)
}

Write-Host "Espace libre : $(Get-EspaceLibre 'C') Go"
Ces blocs ne s'exécutent pas dans cette page
Les extraits PowerShell et bash sont donnés à lire, puis à reproduire sur une machine d'entraînement. Seuls les blocs Python et SQL s'exécutent dans le navigateur.
Les blocs Python de ce chapitre, eux, tournent : ils servent à éprouver la logique d'un script, ce qui est la partie qu'on rate le plus souvent.

Le pipeline

C'est la construction qui donne sa puissance au langage : la sortie d'une cmdlet devient l'entrée de la suivante, sous forme d'objets et non de texte.

Terminal3 commandes
# Les cinq processus qui consomment le plus de memoire
$Get-Process | Sort-Object WorkingSet -Descending | Select-Object -First 5 Name, WorkingSet
# Les services arretes dont le demarrage est automatique : une anomalie
$Get-Service | Where-Object { $_.StartType -eq 'Automatic' -and $_.Status -ne 'Running' }
# Exporter vers un fichier exploitable
$Get-Service | Where-Object { $_.Status -eq 'Stopped' } | Export-Csv services.csv -NoTypeInformation
Ce que fait -NoTypeInformation, et pourquoi il compte
Sans lui, Windows PowerShell écrit en première ligne du fichier une ligne technique décrivant le type d'objet .NET exporté, du genre #TYPE System.ServiceProcess.ServiceController. Cette ligne n'est pas un en-tête de colonnes : elle décale tout, et le tableur comme le script qui relit le fichier prennent alors les vrais noms de colonnes pour des données.
L'argument supprime cette ligne et produit un CSV ordinaire, lisible par un tableur, par un moteur SQL ou par un autre script. C'est une habitude à prendre systématiquement dès qu'un fichier sort de PowerShell pour entrer ailleurs.
Sur les versions récentes de PowerShell, cette ligne n'est plus écrite par défaut et l'argument ne fait plus rien. Le conserver reste sans risque, et garantit le même résultat quelle que soit la version en service, ce qui est exactement ce qu'on attend d'un script d'administration.
Objets contre texte, la différence pratique
Sous Linux, une commande rend du texte, et l'on découpe ce texte avec awk ou cut pour en extraire une colonne. Le script casse le jour où la mise en forme change.
En PowerShell, $_.Status désigne une propriété de l'objet. Il n'y a rien à découper, et rien à casser. C'est ce qui rend les scripts d'administration Windows plus robustes qu'on ne s'y attend.

Administrer l'annuaire par script

Le module Active Directory fournit les cmdlets qui recouvrent tout le cycle de vie d'un compte.

BesoinCmdlet
Créer un compteNew-ADUser
Chercher des comptesGet-ADUser -Filter
Modifier un attributSet-ADUser
Ajouter à un groupeAdd-ADGroupMember
DésactiverDisable-ADAccount
Déplacer dans une OUMove-ADObject
# Creer un compte dans la bonne unite d'organisation
New-ADUser -Name "Marie Dupont" `
    -SamAccountName "m.dupont" `
    -UserPrincipalName "m.dupont@ardexia.lan" `
    -Path "OU=Facturation,OU=Utilisateurs,DC=ardexia,DC=lan" `
    -AccountPassword (Read-Host -AsSecureString "Mot de passe") `
    -ChangePasswordAtLogon $true `
    -Enabled $true

Add-ADGroupMember -Identity "GG_Facturation" -Members "m.dupont"

# Les comptes encore actifs, mais inutilises depuis 90 jours :
# la revue que personne ne fait
Search-ADAccount -UsersOnly -AccountInactive -TimeSpan 90.00:00:00 |
    Where-Object { $_.Enabled } |
    Select-Object Name, SamAccountName, LastLogonDate |
    Export-Csv comptes-inactifs.csv -NoTypeInformation
Le filtre qui ne filtre rien
La forme qui vient spontanément est Get-ADUser -Filter { LastLogonDate -lt $Limite }, et elle ne fonctionne pas. LastLogonDate est un attribut construit : il n'existe pas dans l'annuaire, il est calculé après coup par le module à partir d'un autre attribut. Un filtre porte sur ce que l'annuaire sait chercher, donc jamais sur lui.
Le symptôme est trompeur : selon la version du module, la commande échoue franchement ou rend une liste vide, ce qu'on lit volontiers comme « aucun compte inactif, tout va bien ». Search-ADAccount existe précisément pour cette question et calcule l'inactivité là où il faut.
La leçon dépasse cette commande : avant d'écrire un filtre, il faut savoir si l'attribut visé est stocké ou calculé.
La requête qui répond à la plainte de la direction
« Il devient compliqué de vérifier que tous les accès d'un partant ont bien été supprimés. » La seconde requête ci-dessus produit, en une commande, la liste des comptes actifs et inutilisés depuis trois mois.
Ce n'est pas un exploit technique : c'est ce que permet un annuaire correctement alimenté. Sans annuaire, la question n'avait pas de réponse. Avec un annuaire et une cmdlet, elle en a une, tous les lundis matin.

La logique d'un script de création de comptes

Diagramme de séquence
ScriptFichierAnnuaireJournallire la ligne suivanteprénom, nom, servicel'identifiant existe-t-il déjàoui ou nonc'est ici que se règle le cas des homonymescréer le compte et l'ajouter à son groupeconsigner ce qui vient d'être fait

Trois interlocuteurs pour une seule ligne du fichier, et l'opération se répète autant de fois qu'il y a de lignes. La consultation avant création est ce qui distingue un script utilisable d'un script qui écrase le compte d'un homonyme.

La partie délicate n'est pas la cmdlet, c'est la règle de nommage et ce qu'elle doit gérer : les accents, les homonymes, la longueur maximale d'un identifiant. C'est cette logique que le bloc Python ci-dessous éprouve, avant de l'écrire en PowerShell.

Ces trois contraintes se combinent, et c'est leur combinaison qui pose problème. L'attribut historique sAMAccountName d'Active Directory est plafonné à 20 caractères, et ce plafond ne se négocie pas. Un identifiant qui atteint déjà 20 caractères ne peut donc pas recevoir en plus le suffixe qui le distinguerait d'un homonyme : il faut d'abord lui retirer autant de caractères que le suffixe en apporte. C'est ce que fait la ligne base[:LONGUEUR_MAX - len(suffixe)] + suffixe, et c'est la seule ligne du script qu'il vaut la peine de relire deux fois. Écrite naïvement, en concaténant le suffixe à la fin, elle produit un identifiant de 21 caractères et un échec de création au dixième homonyme, jamais au premier.

main.py
Sortie
>_ Prêt à exécuter…
Les trois cas que les scripts de création oublient
Les accents, qui produisent des identifiants illisibles ou refusés. Les homonymes, qui font échouer la création du second compte, ou pire, écrasent le premier. Et la longueur maximale de 20 caractères, qui tronque silencieusement et crée des collisions inattendues.
Ces trois cas n'apparaissent jamais lors du test avec « Jean Dupont ». Ils apparaissent en production, la semaine de la rentrée.

Côté Linux

Les mêmes besoins, avec d'autres outils. Un script bash, planifié par cron.

#!/bin/bash
# Surveillance de l'espace disque, alerte au-dela d'un seuil

SEUIL=85
JOURNAL=/var/log/espace.log
DESTINATAIRE="admin@ardexia.lan"

df -h --output=pcent,target | tail -n +2 | while read pourcent point; do
    valeur=${pourcent%\%}          # retire le signe %
    valeur=${valeur// /}           # retire les espaces d'alignement
    if [ "$valeur" -ge "$SEUIL" ]; then
        echo "$(date '+%F %T') $point a ${valeur}%" >> "$JOURNAL"
        echo "$point occupe a ${valeur}%" \
            | mail -s "Disque plein" "$DESTINATAIRE"
    fi
done

La planification par cron se lit dans cinq champs, suivis de la commande à exécuter.

# m  h  jm mois js   commande
  0  6  *  *   *     /opt/scripts/espace-disque.sh
  0  2  *  *   0     /opt/scripts/sauvegarde-complete.sh
  0  2  *  *   1-6   /opt/scripts/sauvegarde-differentielle.sh
 */15 * *  *   *     /opt/scripts/verifie-services.sh
Lire une ligne de cron
De gauche à droite : minute, heure, jour du mois, mois, jour de la semaine. L'étoile signifie « toutes les valeurs », et le dimanche porte le numéro 0.
La ligne 0 2 * * 0 se lit donc : à 2 h 00, tous les jours du mois, tous les mois, le dimanche seulement. C'est la sauvegarde complète hebdomadaire du chapitre 5. La ligne suivante, 1-6, couvre les six autres jours de la semaine : ce sont les différentielles, et deux lignes suffisent à écrire toute la politique.

Rendre un script digne de confiance

Un script qui modifie l'annuaire ou supprime des fichiers mérite trois garanties. Aucune n'est optionnelle.

Les trois garanties

Mode simulation : le script sait afficher ce qu'il ferait, sans le faire. En PowerShell, -WhatIf sur les cmdlets qui le supportent.

Journalisation : chaque action écrite dans un fichier daté, avec qui, quoi, et le résultat. C'est ce qui permet de comprendre après coup, et c'est la traçabilité du chapitre 3.

Idempotence : exécuter deux fois le script produit le même état qu'une fois. Un script qui ajoute un utilisateur à un groupe doit vérifier qu'il n'y est pas déjà, plutôt que d'échouer ou de dupliquer.

Un script est plus dangereux qu'un humain
Un technicien qui se trompe crée un mauvais compte. Un script qui se trompe en crée trois cents, ou en désactive trois cents, en quelques secondes et sans hésiter.
D'où la règle : tout script destructeur commence par une exécution en simulation, sur un périmètre réduit, dont on relit la sortie. Et il journalise, pour qu'on puisse défaire.

Exercices type

Que fait `Get-Service | Where-Object { $_.Status -eq 'Stopped' }` ?

Get-Service produit la liste des services sous forme d'objets. Le pipeline la transmet à Where-Object, qui filtre.

$_ désigne l'objet en cours de traitement, et $_.Status sa propriété d'état. La commande rend donc les services arrêtés.

Le point de méthode : on n'a rien découpé ni analysé textuellement. Si le format d'affichage de Get-Service change demain, le filtre continue de fonctionner, parce qu'il porte sur une propriété et non sur une colonne.

Deux salariés s'appellent Dupont. Que doit faire le script de création ?

Détecter la collision avant de créer, et dériver un identifiant distinct : m.dupont puis m.dupont2.

Ce qu'il ne doit surtout pas faire : échouer en laissant la moitié du traitement fait, ou créer un doublon qui rendra les droits impossibles à suivre.

Deux précautions supplémentaires que montre le bloc Python : normaliser les accents, car Mélanie ne doit pas produire un identifiant différent selon l'encodage ; et respecter la limite de 20 caractères, en tronquant avant d'ajouter le suffixe, sinon le suffixe se fait couper.

Pourquoi journaliser un script qui fonctionne bien ?

Parce que la question ne se pose jamais pendant qu'il fonctionne bien.

Elle se pose trois semaines plus tard : « qui a désactivé ce compte ? », « quand ce dossier a-t-il été supprimé ? », « le script a-t-il tourné dimanche ? ». Sans journal, la réponse est « on ne sait pas ».

Un journal utile contient la date, le compte qui a exécuté, l'action tentée, le résultat, et les éléments concernés. Il se conserve avec une durée décidée, comme l'audit du chapitre 3.

Qu'est-ce qu'un script idempotent, et pourquoi est-ce important ?

Un script dont la deuxième exécution produit le même état que la première.

Contre-exemple : un script qui ajoute une ligne à un fichier de configuration. Exécuté trois fois, il produit trois lignes, et une configuration invalide.

Version idempotente : vérifier si la ligne existe déjà, et ne l'ajouter que sinon.

Cela compte parce qu'un script est relancé plus souvent qu'on ne le prévoit : après une interruption, par erreur, ou parce que personne ne sait s'il a déjà tourné. L'idempotence rend la relance sans danger, et c'est ce qui permet d'automatiser sereinement.

Lire `0 2 * * 1-6 /opt/scripts/sauvegarde-differentielle.sh`

À 2 h 00, tous les jours du mois, tous les mois, du lundi au samedi.

Combinée à la ligne 0 2 * * 0 qui lance la sauvegarde complète le dimanche, on obtient exactement la politique du chapitre 5 : une complète hebdomadaire, six différentielles.

Une vérification à faire : que les deux tâches ne se chevauchent pas, et que la fenêtre de 2 h suffit à la complète. Sinon, la différentielle du lundi démarre pendant que la complète du dimanche tourne encore.

Faut-il automatiser la suppression des comptes inactifs ?

La détection, oui, et sans hésiter : c'est une requête, elle est sans risque, et elle produit la liste que personne ne prend le temps d'établir.

La suppression, non. Une suppression automatique de comptes est irréversible et se trompera un jour : un congé de longue durée, un compte de service qui n'ouvre jamais de session, un salarié en mission.

La démarche raisonnable est graduée : détecter automatiquement, désactiver après validation humaine, et supprimer après un délai de conservation, une fois les données personnelles traitées conformément au chapitre 6.

Le principe général : automatiser ce qui se vérifie, faire valider ce qui se défait mal.

Vérification

Vérification rapideon peut se reprendre

1.Quel est le principal apport de l'automatisation ?

2.La règle « première lettre du prénom, puis nom » échoue sur quels cas ?

3.En quoi un script est-il plus dangereux qu'un technicien ?

4.À quoi sert un essai à blanc ?

La méthode

  1. Écrire la règle avant le script. Nommage, cas particuliers, ce qui se passe si l'objet existe déjà.
  2. Éprouver la logique sur des données de test qui contiennent les cas tordus : accents, homonymes, noms trop longs.
  3. Commencer par une simulation sur un périmètre réduit, et relire la sortie ligne à ligne.
  4. Journaliser chaque action avec sa date, son auteur et son résultat.
  5. Rendre le script idempotent : il doit pouvoir être relancé sans dégât.
  6. Automatiser la détection, faire valider la destruction.
  7. Ranger le script quelque part de partagé et versionné. Un script sur le poste d'un technicien n'est pas une procédure d'entreprise.

Synthèse

  • L'automatisation apporte surtout la reproductibilité : une procédure écrite dans un script ne dérive pas.
  • PowerShell manipule des objets, pas du texte. Cmdlets en Verbe-Nom, composées par le pipeline.
  • Le module Active Directory couvre tout le cycle de vie : New-ADUser, Get-ADUser, Add-ADGroupMember, Disable-ADAccount.
  • Une règle de nommage doit gérer les accents, les homonymes et la longueur maximale.
  • Sous Linux, bash et cron en cinq champs : minute, heure, jour du mois, mois, jour de la semaine.
  • Trois garanties : simulation, journalisation, idempotence.
  • Un script se trompe plus vite et plus largement qu'un humain. Il se teste en conséquence.

Mettre en pratique