EExploitation

Scripting — PowerShell et Bash

Automatisation d'administration : scripts PowerShell et Bash, gestion utilisateurs, deploiement, taches planifiees

59 minIntermediaire

Partie 1 — Bash

1.1 Fondamentaux du shell Bash

Variables


nom="Alice"
age=25

# Lecture
echo $nom
echo "Bonjour $nom, vous avez $age ans"

# Variable en lecture seule
readonly SERVEUR="srv-prod-01"

# Supprimer une variable
unset age

# Variables d'environnement
export JAVA_HOME="/usr/lib/jvm/java-17"

Types de variables :

TypeExempleDescription
Chainenom="texte"Par defaut, tout est chaine
Entierdeclare -i x=10Force le type entier
Tableautab=(a b c)Tableau indexe
Tableau associatifdeclare -A hTableau cle-valeur (Bash 4+)

Guillemets et echappement

nom="monde"
echo "Bonjour $nom"      # Bonjour monde (interpolation)
echo 'Bonjour $nom'      # Bonjour $nom (litteral)
echo "Chemin: \$HOME"    # Chemin: $HOME (echappement)
resultat=$(date +%Y-%m-%d)  # Substitution de commande
resultat_ancien=`date`       # Syntaxe ancienne (deconseille)

Tableaux

# Declaration
fruits=("pomme" "banane" "cerise")

# Acces
echo ${fruits[0]}       # pomme
echo ${fruits[@]}       # tous les elements
echo ${#fruits[@]}      # nombre d'elements (3)

# Ajout
fruits+=("fraise")

# Boucle sur un tableau
for f in "${fruits[@]}"; do
    echo "Fruit : $f"
done

# Tableau associatif
declare -A serveurs
serveurs[web]="192.168.1.10"
serveurs[bdd]="192.168.1.20"
serveurs[mail]="192.168.1.30"

echo ${serveurs[web]}         # 192.168.1.10
echo ${!serveurs[@]}          # web bdd mail (les cles)

1.2 Arguments et parametres speciaux

VariableDescription
$0Nom du script
$1, $2, ...Arguments positionnels
$#Nombre d'arguments
$@Tous les arguments (chacun entre guillemets)
$*Tous les arguments (en une seule chaine)
$?Code de retour de la derniere commande (0 = succes)
$$PID du processus courant
$!PID du dernier processus en arriere-plan
#!/bin/bash
# script_args.sh - Demonstration des arguments

echo "Nom du script : $0"
echo "Premier argument : $1"
echo "Deuxieme argument : $2"
echo "Nombre d'arguments : $#"
echo "Tous les arguments : $@"

# Verification du nombre d'arguments
if [ $# -lt 2 ]; then
    echo "Usage: $0 <nom> <prenom>"
    exit 1
fi

Difference entre $@ et $* :

# Avec les arguments : "Jean Pierre" "Marie"
for arg in "$@"; do
    echo "-> $arg"
done
# -> Jean Pierre
# -> Marie

for arg in "$*"; do
    echo "-> $arg"
done
# -> Jean Pierre Marie

1.3 Conditions

Test avec crochets

# Syntaxe [ ] (commande test)
if [ "$nom" = "Alice" ]; then
    echo "Bonjour Alice"
fi

# Syntaxe [[ ]] (Bash etendu, recommande)
if [[ "$nom" == "Alice" ]]; then
    echo "Bonjour Alice"
fi

Operateurs de comparaison

Chaines :

OperateurSignification
= ou ==Egal
!=Different
-z "$var"Chaine vide
-n "$var"Chaine non vide

Entiers :

OperateurSignification
-eqEgal
-neDifferent
-gtSuperieur
-geSuperieur ou egal
-ltInferieur
-leInferieur ou egal

Fichiers :

OperateurSignification
-fFichier regulier existe
-dRepertoire existe
-eExiste (fichier ou repertoire)
-rLisible
-wAccessible en ecriture
-xExecutable
-sTaille superieure a 0
-LLien symbolique
#!/bin/bash
# Conditions combinees
fichier="/etc/passwd"

if [[ -f "$fichier" && -r "$fichier" ]]; then
    echo "Le fichier existe et est lisible"
elif [[ -f "$fichier" ]]; then
    echo "Le fichier existe mais n'est pas lisible"
else
    echo "Le fichier n'existe pas"
fi

# Operateurs logiques
# [[ ]] : && et ||
# [ ] : -a et -o

Structure case

read -p "Entrez un choix (o/n/q) : " choix
case "$choix" in
    o|O|oui)
        echo "Vous avez dit oui"
        ;;
    n|N|non)
        echo "Vous avez dit non"
        ;;
    q|Q|quitter)
        echo "Au revoir"
        exit 0
        ;;
    *)
        echo "Choix invalide"
        ;;
esac

1.4 Boucles

# for classique
for i in 1 2 3 4 5; do
    echo "Iteration $i"
done

# for avec sequence
for i in $(seq 1 10); do
    echo "Numero $i"
done

# for de style C
for ((i=0; i<10; i++)); do
    echo "Index $i"
done

# for sur des fichiers
for fichier in /var/log/*.log; do
    echo "Taille de $fichier : $(du -h "$fichier" | cut -f1)"
done

# while
compteur=0
while [ $compteur -lt 5 ]; do
    echo "Compteur : $compteur"
    ((compteur++))
done

# until (inverse de while)
until [ $compteur -eq 0 ]; do
    ((compteur--))
    echo "Decompte : $compteur"
done

# Lecture ligne par ligne d'un fichier
while IFS= read -r ligne; do
    echo "Ligne : $ligne"
done < /etc/passwd

# Boucle infinie avec break
while true; do
    read -p "Commande (quit pour sortir) : " cmd
    if [[ "$cmd" == "quit" ]]; then
        break
    fi
    echo "Vous avez tape : $cmd"
done

1.5 Fonctions

# Declaration
ma_fonction() {
    echo "Bonjour depuis la fonction"
}

# Avec arguments
saluer() {
    local nom="$1"    # Variable locale
    local prenom="$2"
    echo "Bonjour $prenom $nom"
}

# Appel
saluer "Dupont" "Jean"

# Retour de valeur
# Les fonctions retournent un code (0-255) via return
# Pour retourner une chaine, utiliser echo + substitution
obtenir_date() {
    echo $(date +%Y-%m-%d)
}
aujourd_hui=$(obtenir_date)
echo "Date : $aujourd_hui"

# Verification avec code de retour
fichier_existe() {
    if [[ -f "$1" ]]; then
        return 0  # succes
    else
        return 1  # echec
    fi
}

if fichier_existe "/etc/hosts"; then
    echo "Le fichier existe"
fi

1.6 Manipulation de fichiers et texte

grep — Recherche de motifs

# Recherche simple
grep "root" /etc/passwd

# Ignorer la casse
grep -i "error" /var/log/syslog

# Inverser la recherche
grep -v "commentaire" fichier.conf

# Afficher les numeros de ligne
grep -n "pattern" fichier.txt

# Compter les occurrences
grep -c "404" access.log

# Recherche recursive dans un repertoire
grep -r "mot_de_passe" /etc/

# Expression reguliere etendue
grep -E "^(root|admin):" /etc/passwd

# Afficher le contexte (3 lignes avant/apres)
grep -B3 -A3 "CRITICAL" /var/log/syslog

sed — Editeur de flux

# Substitution (premiere occurrence par ligne)
sed 's/ancien/nouveau/' fichier.txt

# Substitution globale (toutes les occurrences)
sed 's/ancien/nouveau/g' fichier.txt

# Modification en place
sed -i 's/ancien/nouveau/g' fichier.txt

# Supprimer des lignes
sed '/^#/d' fichier.conf          # lignes commencant par #
sed '/^$/d' fichier.txt           # lignes vides
sed '5d' fichier.txt              # ligne 5
sed '3,7d' fichier.txt            # lignes 3 a 7

# Inserer/ajouter
sed '2i\Nouvelle ligne' fichier.txt    # inserer avant ligne 2
sed '2a\Nouvelle ligne' fichier.txt    # ajouter apres ligne 2

# Afficher uniquement certaines lignes
sed -n '10,20p' fichier.txt       # lignes 10 a 20

# Combinaison de commandes
sed -e 's/foo/bar/g' -e '/^$/d' fichier.txt

awk — Traitement de colonnes

# Afficher une colonne
awk '{print $1}' fichier.txt              # premiere colonne
awk '{print $1, $3}' fichier.txt          # colonnes 1 et 3

# Separateur personnalise
awk -F':' '{print $1, $3}' /etc/passwd    # login et UID

# Condition
awk -F':' '$3 >= 1000 {print $1}' /etc/passwd  # utilisateurs UID >= 1000

# Variables speciales
# NR : numero de ligne
# NF : nombre de champs
# $0 : ligne complete
awk '{print NR": "$0}' fichier.txt        # numerotation des lignes

# Calculs
awk '{somme += $1} END {print "Total:", somme}' nombres.txt

# Formatage
awk -F':' '{printf "%-15s UID=%-5s GID=%s\n", $1, $3, $4}' /etc/passwd

# BEGIN et END
awk 'BEGIN {print "=== RAPPORT ==="} {print $0} END {print "=== FIN ==="}' fichier.txt

cut, sort, uniq, tr, wc

# cut — extraire des colonnes
cut -d':' -f1 /etc/passwd           # champ 1, delimiteur :
cut -d',' -f1,3 fichier.csv         # champs 1 et 3
cut -c1-10 fichier.txt              # caracteres 1 a 10

# sort — trier
sort fichier.txt                    # tri alphabetique
sort -n fichier.txt                 # tri numerique
sort -r fichier.txt                 # tri inverse
sort -t':' -k3 -n /etc/passwd      # tri par 3e champ numerique
sort -u fichier.txt                 # tri + suppression doublons

# uniq — supprimer les doublons consecutifs
sort fichier.txt | uniq             # doublons supprimes
sort fichier.txt | uniq -c          # compter les occurrences
sort fichier.txt | uniq -d          # afficher uniquement les doublons

# tr — transformer des caracteres
echo "HELLO" | tr 'A-Z' 'a-z'      # minuscules
echo "abc" | tr 'a-z' 'A-Z'        # majuscules
cat fichier.txt | tr -d '\r'        # supprimer les retours chariot Windows
echo "a::b:::c" | tr -s ':'        # comprimer les repetitions -> a:b:c

# wc — compter
wc -l fichier.txt                   # nombre de lignes
wc -w fichier.txt                   # nombre de mots
wc -c fichier.txt                   # nombre d'octets

Redirections et tubes

# Redirections
commande > fichier          # stdout vers fichier (ecrase)
commande >> fichier         # stdout vers fichier (ajoute)
commande 2> erreurs.log     # stderr vers fichier
commande &> tout.log        # stdout + stderr vers fichier
commande 2>&1               # stderr vers stdout
commande < entree.txt       # stdin depuis fichier

# Here document
cat <<EOF > /etc/motd
Bienvenue sur le serveur
Maintenance prevue le dimanche
EOF

# Here string
grep "motif" <<< "chaine a chercher"

# Tubes (pipeline)
cat /var/log/syslog | grep "error" | sort | uniq -c | sort -rn | head -10

# tee — ecrire dans un fichier ET afficher
commande | tee fichier.log
commande | tee -a fichier.log    # ajouter au lieu d'ecraser

# xargs — construire des commandes
find /tmp -name "*.tmp" -print0 | xargs -0 rm -f
cat liste_serveurs.txt | xargs -I{} ssh {} "uptime"

1.7 Scripts d'administration Bash

Creation d'utilisateurs en masse

#!/bin/bash
# creation_utilisateurs.sh
# Cree des utilisateurs a partir d'un fichier CSV
# Format CSV : login,prenom,nom,groupe

FICHIER_CSV="$1"
LOG="/var/log/creation_users.log"

if [[ ! -f "$FICHIER_CSV" ]]; then
    echo "Erreur : fichier $FICHIER_CSV introuvable"
    exit 1
fi

echo "=== Creation d'utilisateurs - $(date) ===" >> "$LOG"

while IFS=',' read -r login prenom nom groupe; do
    # Ignorer l'en-tete
    [[ "$login" == "login" ]] && continue
    # Ignorer les lignes vides
    [[ -z "$login" ]] && continue

    # Verifier si l'utilisateur existe deja
    if id "$login" &>/dev/null; then
        echo "[SKIP] $login existe deja" | tee -a "$LOG"
        continue
    fi

    # Creer le groupe si necessaire
    if ! getent group "$groupe" &>/dev/null; then
        groupadd "$groupe"
        echo "[INFO] Groupe $groupe cree" >> "$LOG"
    fi

    # Creer l'utilisateur
    useradd -m -g "$groupe" -c "$prenom $nom" -s /bin/bash "$login"
    if [[ $? -eq 0 ]]; then
        # Generer un mot de passe temporaire
        mdp=$(openssl rand -base64 12)
        echo "$login:$mdp" | chpasswd
        # Forcer le changement au prochain login
        chage -d 0 "$login"
        echo "[OK] $login cree (mdp: $mdp)" | tee -a "$LOG"
    else
        echo "[ERREUR] Echec creation de $login" | tee -a "$LOG"
    fi
done < "$FICHIER_CSV"

echo "=== Fin - $(date) ===" >> "$LOG"

Sauvegarde automatisee

#!/bin/bash
# sauvegarde.sh
# Sauvegarde incrementielle avec rotation

# Configuration
SRC="/home /etc /var/www"
DEST="/backup"
RETENTION=7                    # jours de retention
DATE=$(date +%Y-%m-%d_%H%M)
ARCHIVE="$DEST/backup_$DATE.tar.gz"
LOG="$DEST/backup_$DATE.log"

# Verification de l'espace disque
espace_libre=$(df -BG "$DEST" | tail -1 | awk '{print $4}' | tr -d 'G')
if [[ "$espace_libre" -lt 10 ]]; then
    echo "ALERTE : Espace disque insuffisant (${espace_libre}G libre)" | \
        mail -s "[BACKUP] Espace insuffisant" admin@exemple.fr
    exit 1
fi

# Creation de la sauvegarde
echo "Debut sauvegarde : $(date)" > "$LOG"
tar -czf "$ARCHIVE" $SRC 2>> "$LOG"

if [[ $? -eq 0 ]]; then
    taille=$(du -h "$ARCHIVE" | cut -f1)
    echo "Sauvegarde reussie : $ARCHIVE ($taille)" >> "$LOG"
else
    echo "ERREUR lors de la sauvegarde" >> "$LOG"
    mail -s "[BACKUP] ECHEC" admin@exemple.fr < "$LOG"
    exit 1
fi

# Rotation : supprimer les sauvegardes de plus de $RETENTION jours
find "$DEST" -name "backup_*.tar.gz" -mtime +$RETENTION -delete
echo "Rotation effectuee (retention : ${RETENTION} jours)" >> "$LOG"

# Verification d'integrite
tar -tzf "$ARCHIVE" > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
    echo "Verification d'integrite OK" >> "$LOG"
else
    echo "ERREUR : archive corrompue" >> "$LOG"
fi

echo "Fin sauvegarde : $(date)" >> "$LOG"

Rotation de logs

#!/bin/bash
# rotation_logs.sh

REPERTOIRE_LOGS="/var/log/application"
RETENTION=30
DATE=$(date +%Y%m%d)

for log in "$REPERTOIRE_LOGS"/*.log; do
    [[ ! -f "$log" ]] && continue

    # Compresser le log courant
    cp "$log" "${log}.${DATE}"
    gzip "${log}.${DATE}"
    # Vider le fichier original (sans supprimer pour les processus qui ecrivent dedans)
    > "$log"

    echo "Rotation effectuee : $log"
done

# Supprimer les anciens logs compresses
find "$REPERTOIRE_LOGS" -name "*.gz" -mtime +$RETENTION -delete
echo "Nettoyage : fichiers de plus de $RETENTION jours supprimes"

Surveillance de l'espace disque

#!/bin/bash
# surveillance_disque.sh

SEUIL=80
ADMIN="admin@exemple.fr"

df -h --output=pcent,target | tail -n +2 | while read -r utilisation montage; do
    # Extraire le pourcentage (supprimer le %)
    pourcent=${utilisation%\%}

    if [[ "$pourcent" -gt "$SEUIL" ]]; then
        echo "ALERTE : $montage utilise a ${pourcent}% (seuil : ${SEUIL}%)"
        echo "Partition $montage a ${pourcent}% sur $(hostname) le $(date)" | \
            mail -s "[DISQUE] Alerte $montage" "$ADMIN"
    fi
done

Rapport systeme

#!/bin/bash
# rapport_systeme.sh

RAPPORT="/tmp/rapport_$(hostname)_$(date +%Y%m%d).txt"

{
    echo "=========================================="
    echo " RAPPORT SYSTEME - $(hostname)"
    echo " Date : $(date)"
    echo "=========================================="

    echo ""
    echo "--- INFORMATIONS GENERALES ---"
    echo "Hostname    : $(hostname)"
    echo "OS          : $(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)"
    echo "Kernel      : $(uname -r)"
    echo "Uptime      : $(uptime -p)"
    echo "Utilisateurs: $(who | wc -l) connecte(s)"

    echo ""
    echo "--- PROCESSEUR ---"
    echo "Modele      : $(grep 'model name' /proc/cpuinfo | head -1 | cut -d':' -f2 | xargs)"
    echo "Coeurs      : $(nproc)"
    echo "Charge      : $(cat /proc/loadavg | cut -d' ' -f1-3)"

    echo ""
    echo "--- MEMOIRE ---"
    free -h | awk 'NR==2{printf "Total: %s | Utilisee: %s | Libre: %s\n", $2, $3, $4}'

    echo ""
    echo "--- ESPACE DISQUE ---"
    df -h --output=source,size,used,avail,pcent,target | head -20

    echo ""
    echo "--- RESEAU ---"
    ip -4 addr show | grep inet | awk '{print $NF": "$2}'

    echo ""
    echo "--- TOP 10 PROCESSUS (CPU) ---"
    ps aux --sort=-%cpu | head -11

    echo ""
    echo "--- SERVICES EN ECHEC ---"
    systemctl --failed --no-pager 2>/dev/null

    echo ""
    echo "=========================================="
    echo " FIN DU RAPPORT"
    echo "=========================================="
} > "$RAPPORT"

echo "Rapport genere : $RAPPORT"

1.8 Cron — Automatisation des scripts

Syntaxe crontab

# Minute  Heure  Jour  Mois  JourSemaine  Commande
#  0-59   0-23   1-31  1-12     0-7
#                                (0 et 7 = dimanche)

# Exemples
* * * * *           # toutes les minutes
0 * * * *           # toutes les heures (a HH:00)
0 2 * * *           # tous les jours a 2h00
0 2 * * 1           # tous les lundis a 2h00
0 0 1 * *           # le 1er de chaque mois a minuit
*/5 * * * *         # toutes les 5 minutes
0 8-18 * * 1-5      # toutes les heures de 8h a 18h en semaine

Gestion de crontab

# Editer la crontab de l'utilisateur courant
crontab -e

# Afficher la crontab courante
crontab -l

# Supprimer la crontab
crontab -r

# Editer la crontab d'un autre utilisateur (root)
crontab -u www-data -e

Exemples concrets

# Sauvegarde quotidienne a 2h00
0 2 * * * /opt/scripts/sauvegarde.sh >> /var/log/sauvegarde.log 2>&1

# Surveillance disque toutes les 15 minutes
*/15 * * * * /opt/scripts/surveillance_disque.sh

# Rapport systeme chaque lundi a 8h00
0 8 * * 1 /opt/scripts/rapport_systeme.sh

# Rotation des logs le 1er de chaque mois
0 0 1 * * /opt/scripts/rotation_logs.sh

# Nettoyage /tmp tous les jours a 3h00
0 3 * * * find /tmp -type f -mtime +7 -delete

Fichiers cron systeme

/etc/crontab             # crontab systeme (avec champ utilisateur)
/etc/cron.d/             # fichiers cron supplementaires
/etc/cron.daily/         # scripts executes quotidiennement
/etc/cron.weekly/        # scripts executes hebdomadairement
/etc/cron.monthly/       # scripts executes mensuellement
/etc/cron.hourly/        # scripts executes chaque heure
/var/spool/cron/crontabs/  # crontabs des utilisateurs

Partie 2 — PowerShell

2.1 Introduction

Pourquoi PowerShell

PowerShell est le shell moderne de Microsoft, concu pour l'administration systeme Windows et, depuis PowerShell Core (6+), multiplateforme. Contrairement a cmd.exe qui manipule du texte brut, PowerShell manipule des objets .NET, ce qui permet un traitement structure et puissant des donnees.

Comparaison

Criterecmd.exeBashPowerShell
Type de donneesTexteTexteObjets .NET
PlateformeWindowsLinux/macOSMultiplateforme (Core 6+)
SyntaxeCommandes DOSCommandes UNIXCmdlets (Verb-Noun)
PipelineTexte brutTexte brutObjets structures
Integration ADNonNon nativementModule ActiveDirectory
Scripting avanceLimiteCompletComplet + acces .NET

Versions

VersionAnneeRemarques
PowerShell 1.02006Version initiale
PowerShell 2.02009Remoting, modules
PowerShell 3.02012Workflows, ISE ameliore
PowerShell 4.02013DSC (Desired State Configuration)
PowerShell 5.12016Derniere version "Windows PowerShell"
PowerShell 6.0 (Core)2018Multiplateforme, open source
PowerShell 7.x2020+Version actuelle recommandee

Important : Windows PowerShell 5.1 (powershell.exe) et PowerShell 7 (pwsh.exe) coexistent. Le BTS utilise principalement Windows PowerShell 5.1.

2.2 Bases

Cmdlets

Toutes les commandes PowerShell suivent la convention Verbe-Nom :

Get-Process          # Obtenir les processus
Stop-Service         # Arreter un service
New-Item             # Creer un element
Remove-Item          # Supprimer un element
Set-Location         # Changer de repertoire (equivalent cd)
Get-Content          # Lire le contenu d'un fichier (equivalent cat)

Verbes courants : Get, Set, New, Remove, Start, Stop, Restart, Test, Import, Export, Invoke, Add, Copy, Move.

Aide integree

# Aide sur une cmdlet
Get-Help Get-Process
Get-Help Get-Process -Detailed
Get-Help Get-Process -Examples
Get-Help Get-Process -Full

# Mettre a jour l'aide
Update-Help

# Rechercher une cmdlet
Get-Command -Verb Get
Get-Command -Noun Process
Get-Command *service*
Get-Command -Module ActiveDirectory

Pipeline

Le pipeline (|) transmet des objets d'une cmdlet a la suivante :

# Lister les processus tries par memoire
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 10 Name, @{N='RAM_MB';E={[math]::Round($_.WorkingSet64/1MB,2)}}

# Lister les services arretes
Get-Service | Where-Object {$_.Status -eq 'Stopped'}

# Exporter vers CSV
Get-Process | Select-Object Name, Id, CPU | Export-Csv -Path "processus.csv" -NoTypeInformation

Alias

# Alias integres courants
# ls    -> Get-ChildItem
# cd    -> Set-Location
# cp    -> Copy-Item
# mv    -> Move-Item
# rm    -> Remove-Item
# cat   -> Get-Content
# echo  -> Write-Output
# cls   -> Clear-Host
# man   -> Get-Help
# where -> Where-Object
# select -> Select-Object
# sort  -> Sort-Object
# ft    -> Format-Table
# fl    -> Format-List

# Voir tous les alias
Get-Alias

# Creer un alias
Set-Alias -Name np -Value notepad.exe

2.3 Variables

# Declaration et affectation
$nom = "Serveur-01"
$port = 443
$actif = $true

# Types
$entier = [int]42
$decimal = [double]3.14
$chaine = [string]"texte"
$date = [datetime]"2025-01-15"
$booleen = [bool]$true

# Verifier le type
$nom.GetType()
$nom.GetType().Name      # String

# Conversion
$nombre = [int]"42"
$texte = [string]42

Tableaux

# Declaration
$serveurs = @("SRV-01", "SRV-02", "SRV-03")
$nombres = 1, 2, 3, 4, 5

# Acces
$serveurs[0]              # SRV-01
$serveurs[-1]             # SRV-03 (dernier)
$serveurs[0..1]           # SRV-01, SRV-02

# Proprietes
$serveurs.Count           # 3
$serveurs.Length           # 3

# Ajout (cree un nouveau tableau)
$serveurs += "SRV-04"

# Tableau vide
$liste = @()

# Parcours
foreach ($srv in $serveurs) {
    Write-Host "Serveur : $srv"
}

# Plage
$plage = 1..100           # 1 a 100

# Verifier la presence
$serveurs -contains "SRV-01"    # True
"SRV-01" -in $serveurs          # True

Hashtables (dictionnaires)

# Declaration
$serveur = @{
    Nom     = "SRV-WEB-01"
    IP      = "192.168.1.10"
    Role    = "Serveur Web"
    OS      = "Windows Server 2022"
    RAM_GB  = 16
}

# Acces
$serveur["Nom"]           # SRV-WEB-01
$serveur.Nom              # SRV-WEB-01

# Modification
$serveur["RAM_GB"] = 32
$serveur.RAM_GB = 32

# Ajout d'une cle
$serveur["Datacenter"] = "Paris"

# Suppression d'une cle
$serveur.Remove("Datacenter")

# Parcours
foreach ($cle in $serveur.Keys) {
    Write-Host "$cle : $($serveur[$cle])"
}

# Hashtable ordonnee
$config = [ordered]@{
    Etape1 = "Sauvegarde"
    Etape2 = "Mise a jour"
    Etape3 = "Redemarrage"
}

2.4 Operateurs

Comparaison

OperateurSignificationExemple
-eqEgal5 -eq 5 -> True
-neDifferent5 -ne 3 -> True
-gtSuperieur5 -gt 3 -> True
-geSuperieur ou egal5 -ge 5 -> True
-ltInferieur3 -lt 5 -> True
-leInferieur ou egal3 -le 5 -> True
-likeCorrespondance joker"Hello" -like "He*" -> True
-notlikeNon-correspondance joker"Hello" -notlike "Wo*" -> True
-matchRegex"abc123" -match "\d+" -> True
-notmatchNon-correspondance regex"abc" -notmatch "\d+" -> True
-containsContient (tableau)@(1,2,3) -contains 2 -> True
-inPresent dans (tableau)2 -in @(1,2,3) -> True
-isType42 -is [int] -> True

Les operateurs sont insensibles a la casse par defaut. Prefixer avec c pour sensibilite : -ceq, -clike, -cmatch.

Logiques

OperateurSignification
-andET logique
-orOU logique
-not ou !NON logique
-xorOU exclusif
if (($age -ge 18) -and ($pays -eq "France")) {
    Write-Host "Majeur en France"
}

Arithmetiques et affectation

$a = 10 + 5      # 15
$a = 10 - 3      # 7
$a = 10 * 2      # 20
$a = 10 / 3      # 3.33...
$a = 10 % 3      # 1 (modulo)

$a += 5           # $a = $a + 5
$a -= 3           # $a = $a - 3
$a++              # increment
$a--              # decrement

2.5 Structures de controle

if / elseif / else

$espace = (Get-PSDrive C).Free / 1GB

if ($espace -lt 5) {
    Write-Host "CRITIQUE : moins de 5 Go disponibles"
} elseif ($espace -lt 20) {
    Write-Host "ATTENTION : moins de 20 Go disponibles"
} else {
    Write-Host "OK : $([math]::Round($espace, 2)) Go disponibles"
}

switch

$jour = (Get-Date).DayOfWeek

switch ($jour) {
    "Monday"    { Write-Host "Lundi : sauvegarde complete" }
    "Wednesday" { Write-Host "Mercredi : sauvegarde incrementielle" }
    "Friday"    { Write-Host "Vendredi : rapport hebdomadaire" }
    default     { Write-Host "Pas de tache planifiee" }
}

# Switch avec regex
$code = "ERR-404"
switch -Regex ($code) {
    "^ERR"  { Write-Host "Erreur detectee" }
    "404"   { Write-Host "Ressource introuvable" }
    "^OK"   { Write-Host "Pas d'erreur" }
}

# Switch avec fichier
switch -File "C:\logs\codes.txt" {
    "200" { "Succes" }
    "404" { "Non trouve" }
    "500" { "Erreur serveur" }
}

Boucles

# for
for ($i = 0; $i -lt 10; $i++) {
    Write-Host "Iteration $i"
}

# foreach
$services = @("wuauserv", "Spooler", "W32Time")
foreach ($svc in $services) {
    $etat = (Get-Service $svc).Status
    Write-Host "$svc : $etat"
}

# ForEach-Object (pipeline)
Get-Service | ForEach-Object {
    Write-Host "$($_.Name) -> $($_.Status)"
}

# while
$tentatives = 0
while ($tentatives -lt 3) {
    $tentatives++
    Write-Host "Tentative $tentatives"
}

# do-while (execute au moins une fois)
do {
    $reponse = Read-Host "Continuer ? (o/n)"
} while ($reponse -ne "n")

# do-until
do {
    $ping = Test-NetConnection -ComputerName "srv-01" -InformationLevel Quiet
    Start-Sleep -Seconds 5
} until ($ping -eq $true)

# break et continue
foreach ($num in 1..20) {
    if ($num -eq 15) { break }      # sort de la boucle
    if ($num % 2 -eq 0) { continue } # passe a l'iteration suivante
    Write-Host $num
}

2.6 Fonctions

# Fonction simple
function Dire-Bonjour {
    Write-Host "Bonjour"
}

# Avec parametres
function Dire-Bonjour {
    param(
        [string]$Nom,
        [string]$Prenom = "Utilisateur"   # valeur par defaut
    )
    Write-Host "Bonjour $Prenom $Nom"
}

Dire-Bonjour -Nom "Dupont" -Prenom "Jean"
Dire-Bonjour -Nom "Dupont"                # utilise la valeur par defaut

# Parametres obligatoires et validation
function New-CompteUtilisateur {
    param(
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Login,

        [Parameter(Mandatory=$true)]
        [ValidateLength(8, 64)]
        [string]$MotDePasse,

        [ValidateSet("Standard", "Admin", "Service")]
        [string]$Type = "Standard",

        [ValidateRange(1, 999)]
        [int]$Quota = 100
    )

    Write-Host "Creation du compte $Login de type $Type avec quota ${Quota}Mo"
    # ... logique de creation
}

# Retour de valeur
function Get-EspaceDisque {
    param([string]$Lecteur = "C")

    $disque = Get-PSDrive $Lecteur
    $libre = [math]::Round($disque.Free / 1GB, 2)
    $total = [math]::Round(($disque.Used + $disque.Free) / 1GB, 2)

    return @{
        Lecteur = $Lecteur
        LibreGB = $libre
        TotalGB = $total
        PourcentUtilise = [math]::Round(($disque.Used / ($disque.Used + $disque.Free)) * 100, 1)
    }
}

$info = Get-EspaceDisque -Lecteur "C"
Write-Host "Disque $($info.Lecteur): $($info.LibreGB) Go libres sur $($info.TotalGB) Go"

Pipeline dans les fonctions

function Get-TailleFichier {
    param(
        [Parameter(ValueFromPipeline=$true)]
        [System.IO.FileInfo]$Fichier
    )

    process {
        [PSCustomObject]@{
            Nom    = $Fichier.Name
            Taille = "{0:N2} Ko" -f ($Fichier.Length / 1KB)
            Date   = $Fichier.LastWriteTime
        }
    }
}

Get-ChildItem C:\Logs -File | Get-TailleFichier | Format-Table

2.7 Objets et pipeline

Manipulation d'objets

# Decouvrir les proprietes et methodes d'un objet
Get-Process | Get-Member
Get-Service | Get-Member -MemberType Property

# Select-Object : choisir les proprietes
Get-Process | Select-Object Name, Id, CPU, WorkingSet64
Get-Process | Select-Object -First 5 Name
Get-Process | Select-Object -Last 3 Name
Get-Process | Select-Object -Property Name, @{Name='RAM_MB'; Expression={[math]::Round($_.WorkingSet64/1MB,2)}}

# Where-Object : filtrer
Get-Service | Where-Object { $_.Status -eq "Running" }
Get-Process | Where-Object { $_.CPU -gt 100 }
Get-EventLog -LogName System -Newest 100 | Where-Object { $_.EntryType -eq "Error" }

# Sort-Object : trier
Get-Process | Sort-Object CPU -Descending
Get-ChildItem | Sort-Object Length -Descending
Get-Service | Sort-Object Status, Name

# Group-Object : regrouper
Get-Service | Group-Object Status
Get-EventLog -LogName System -Newest 1000 | Group-Object EntryType

# Measure-Object : statistiques
Get-ChildItem C:\Windows -File | Measure-Object Length -Sum -Average -Maximum -Minimum

Formatage de sortie

# Format-Table : tableau (par defaut pour peu de proprietes)
Get-Service | Format-Table Name, Status, StartType -AutoSize

# Format-List : liste (par defaut pour beaucoup de proprietes)
Get-Service wuauserv | Format-List *

# Format-Wide : une seule propriete en colonnes
Get-Service | Format-Wide Name -Column 4

# Out-GridView : interface graphique (Windows)
Get-Process | Out-GridView

Import et export

# CSV
Get-Process | Select-Object Name, Id, CPU | Export-Csv "processus.csv" -NoTypeInformation -Delimiter ";"
$donnees = Import-Csv "processus.csv" -Delimiter ";"

# JSON
Get-Service | ConvertTo-Json | Out-File "services.json"
$services = Get-Content "services.json" | ConvertFrom-Json

# XML
Get-Process | Export-Clixml "processus.xml"
$proc = Import-Clixml "processus.xml"

# HTML
Get-Service | ConvertTo-Html -Title "Services" | Out-File "services.html"

# Fichier texte
Get-Content "fichier.txt"
Set-Content "fichier.txt" -Value "Contenu"
Add-Content "fichier.txt" -Value "Ligne ajoutee"

2.8 Gestion Active Directory

Le module ActiveDirectory doit etre installe (RSAT ou Windows Server avec le role AD DS).

# Importer le module
Import-Module ActiveDirectory

Gestion des utilisateurs

# Lister tous les utilisateurs
Get-ADUser -Filter *
Get-ADUser -Filter * -Properties DisplayName, EmailAddress, Enabled | Select-Object SamAccountName, DisplayName, Enabled

# Rechercher un utilisateur
Get-ADUser -Identity "jdupont"
Get-ADUser -Filter {Name -like "Dupont*"}
Get-ADUser -Filter {Enabled -eq $false}    # comptes desactives

# Recherche dans une OU specifique
Get-ADUser -SearchBase "OU=Comptabilite,DC=entreprise,DC=local" -Filter *

# Creer un utilisateur
New-ADUser -Name "Jean Dupont" `
    -SamAccountName "jdupont" `
    -UserPrincipalName "jdupont@entreprise.local" `
    -GivenName "Jean" `
    -Surname "Dupont" `
    -DisplayName "Jean Dupont" `
    -Path "OU=Comptabilite,DC=entreprise,DC=local" `
    -AccountPassword (ConvertTo-SecureString "P@ssw0rd2025!" -AsPlainText -Force) `
    -Enabled $true `
    -ChangePasswordAtLogon $true

# Modifier un utilisateur
Set-ADUser -Identity "jdupont" -Title "Comptable" -Department "Finance" -Office "Paris"
Set-ADUser -Identity "jdupont" -Enabled $false    # desactiver

# Reinitialiser le mot de passe
Set-ADAccountPassword -Identity "jdupont" `
    -NewPassword (ConvertTo-SecureString "NouveauMdp2025!" -AsPlainText -Force) `
    -Reset
Set-ADUser -Identity "jdupont" -ChangePasswordAtLogon $true

# Supprimer un utilisateur
Remove-ADUser -Identity "jdupont" -Confirm:$false

# Deverrouiller un compte
Unlock-ADAccount -Identity "jdupont"

Gestion des groupes

# Lister les groupes
Get-ADGroup -Filter *
Get-ADGroup -Filter {Name -like "GRP_*"}

# Creer un groupe
New-ADGroup -Name "GRP_Comptabilite" `
    -GroupCategory Security `
    -GroupScope Global `
    -Path "OU=Groupes,DC=entreprise,DC=local" `
    -Description "Groupe du service Comptabilite"

# Ajouter un membre
Add-ADGroupMember -Identity "GRP_Comptabilite" -Members "jdupont", "mmartin"

# Lister les membres d'un groupe
Get-ADGroupMember -Identity "GRP_Comptabilite" | Select-Object Name, SamAccountName

# Retirer un membre
Remove-ADGroupMember -Identity "GRP_Comptabilite" -Members "jdupont" -Confirm:$false

# Groupes d'un utilisateur
Get-ADPrincipalGroupMembership -Identity "jdupont" | Select-Object Name

Gestion des ordinateurs

# Lister les ordinateurs
Get-ADComputer -Filter * -Properties OperatingSystem, LastLogonDate | Select-Object Name, OperatingSystem, LastLogonDate

# Ordinateurs inactifs depuis 90 jours
$date = (Get-Date).AddDays(-90)
Get-ADComputer -Filter {LastLogonDate -lt $date} -Properties LastLogonDate

2.9 Gestion des fichiers et dossiers

# Lister le contenu
Get-ChildItem C:\Users                          # ls
Get-ChildItem C:\Logs -Recurse -Filter "*.log"  # recursif + filtre
Get-ChildItem C:\Logs -File                      # fichiers uniquement
Get-ChildItem C:\Logs -Directory                 # dossiers uniquement

# Creer
New-Item -Path "C:\Scripts" -ItemType Directory
New-Item -Path "C:\Scripts\test.ps1" -ItemType File -Value "Write-Host 'Test'"

# Copier
Copy-Item "C:\source\fichier.txt" -Destination "C:\dest\"
Copy-Item "C:\source\*" -Destination "C:\dest\" -Recurse

# Deplacer
Move-Item "C:\temp\rapport.txt" -Destination "C:\archive\"

# Renommer
Rename-Item "C:\fichier.txt" -NewName "fichier_old.txt"

# Supprimer
Remove-Item "C:\temp\*.tmp" -Force
Remove-Item "C:\ancien_dossier" -Recurse -Force

# Verifier l'existence
Test-Path "C:\Scripts\test.ps1"        # True ou False
Test-Path "C:\Scripts" -PathType Container

# Contenu de fichiers
Get-Content "C:\logs\app.log"
Get-Content "C:\logs\app.log" -Tail 20               # 20 dernieres lignes
Get-Content "C:\logs\app.log" -Wait                   # equivalent tail -f
Set-Content "C:\config.txt" -Value "parametre=valeur"
Add-Content "C:\config.txt" -Value "nouveau_parametre=42"

2.10 Gestion des services

# Lister les services
Get-Service
Get-Service | Where-Object { $_.Status -eq "Running" }
Get-Service -Name "wuauserv"
Get-Service -DisplayName "*Windows Update*"

# Demarrer / Arreter / Redemarrer
Start-Service -Name "Spooler"
Stop-Service -Name "Spooler" -Force
Restart-Service -Name "Spooler"

# Changer le type de demarrage
Set-Service -Name "Spooler" -StartupType Automatic
Set-Service -Name "Spooler" -StartupType Disabled
Set-Service -Name "Spooler" -StartupType Manual

# Verifier un service sur une machine distante
Get-Service -Name "wuauserv" -ComputerName "SRV-01"

# Script de verification de services critiques
$servicesCritiques = @("DNS", "DHCP", "W32Time", "Netlogon")
foreach ($svc in $servicesCritiques) {
    $etat = Get-Service -Name $svc -ErrorAction SilentlyContinue
    if ($null -eq $etat) {
        Write-Host "[ABSENT] $svc n'existe pas sur cette machine"
    } elseif ($etat.Status -ne "Running") {
        Write-Host "[ARRETE] $svc est arrete, tentative de redemarrage..."
        Start-Service -Name $svc
    } else {
        Write-Host "[OK] $svc est en cours d'execution"
    }
}

2.11 Gestion reseau

# Test de connectivite
Test-NetConnection -ComputerName "192.168.1.1"
Test-NetConnection -ComputerName "srv-01" -Port 3389
Test-NetConnection -ComputerName "google.com" -InformationLevel Detailed

# Test ping simple (retourne booleen)
Test-NetConnection -ComputerName "srv-01" -InformationLevel Quiet

# Configuration IP
Get-NetIPAddress
Get-NetIPAddress -InterfaceAlias "Ethernet" -AddressFamily IPv4
Get-NetIPConfiguration

# DNS
Resolve-DnsName "google.com"
Resolve-DnsName "google.com" -Type MX
Resolve-DnsName "192.168.1.1"    # reverse lookup

# Adaptateurs reseau
Get-NetAdapter
Get-NetAdapter | Where-Object { $_.Status -eq "Up" }

# Table de routage
Get-NetRoute

# Ports en ecoute
Get-NetTCPConnection -State Listen
Get-NetTCPConnection -LocalPort 80

# Scanner de ports simple
function Test-PortRange {
    param(
        [string]$Cible,
        [int]$PortDebut = 1,
        [int]$PortFin = 1024
    )
    $PortDebut..$PortFin | ForEach-Object {
        $result = Test-NetConnection -ComputerName $Cible -Port $_ -WarningAction SilentlyContinue -InformationLevel Quiet
        if ($result) {
            Write-Host "Port $_ : OUVERT"
        }
    }
}

2.12 Scripts concrets PowerShell

Creation d'utilisateurs AD en masse depuis un CSV

Fichier utilisateurs.csv :

Login;Prenom;Nom;Service;Fonction;Mail
jdupont;Jean;Dupont;Comptabilite;Comptable;jdupont@entreprise.local
mmartin;Marie;Martin;RH;Gestionnaire RH;mmartin@entreprise.local
pdurand;Pierre;Durand;Informatique;Technicien;pdurand@entreprise.local

Script :

# Creation_Utilisateurs_AD.ps1
# Creation en masse d'utilisateurs Active Directory depuis un fichier CSV

param(
    [Parameter(Mandatory=$true)]
    [string]$FichierCSV,

    [string]$OUBase = "OU=Utilisateurs,DC=entreprise,DC=local",
    [string]$Domaine = "entreprise.local",
    [string]$FichierLog = "C:\Logs\creation_ad_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
)

Import-Module ActiveDirectory

# Fonction de journalisation
function Write-Log {
    param([string]$Message, [string]$Niveau = "INFO")
    $horodatage = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $ligne = "[$horodatage] [$Niveau] $Message"
    Add-Content -Path $FichierLog -Value $ligne
    switch ($Niveau) {
        "ERREUR"  { Write-Host $ligne -ForegroundColor Red }
        "OK"      { Write-Host $ligne -ForegroundColor Green }
        "SKIP"    { Write-Host $ligne -ForegroundColor Yellow }
        default   { Write-Host $ligne }
    }
}

# Verification du fichier CSV
if (-not (Test-Path $FichierCSV)) {
    Write-Log "Fichier $FichierCSV introuvable" "ERREUR"
    exit 1
}

$utilisateurs = Import-Csv -Path $FichierCSV -Delimiter ";"
$compteurOK = 0
$compteurErreur = 0
$compteurSkip = 0

Write-Log "Debut du traitement de $($utilisateurs.Count) utilisateurs"

foreach ($u in $utilisateurs) {
    $login = $u.Login
    $prenom = $u.Prenom
    $nom = $u.Nom
    $service = $u.Service
    $fonction = $u.Fonction
    $mail = $u.Mail

    # Verifier si l'utilisateur existe deja
    try {
        $existant = Get-ADUser -Identity $login -ErrorAction Stop
        Write-Log "L'utilisateur $login existe deja" "SKIP"
        $compteurSkip++
        continue
    } catch {
        # L'utilisateur n'existe pas, on peut le creer
    }

    # Creer l'OU du service si elle n'existe pas
    $ouService = "OU=$service,$OUBase"
    if (-not ([adsi]::Exists("LDAP://$ouService"))) {
        try {
            New-ADOrganizationalUnit -Name $service -Path $OUBase
            Write-Log "OU $service creee"
        } catch {
            Write-Log "Erreur creation OU $service : $_" "ERREUR"
        }
    }

    # Generer un mot de passe temporaire
    $mdpClair = "Tmp-" + (Get-Random -Minimum 100000 -Maximum 999999) + "!"
    $mdpSecure = ConvertTo-SecureString $mdpClair -AsPlainText -Force

    # Creer l'utilisateur
    try {
        New-ADUser `
            -SamAccountName $login `
            -UserPrincipalName "$login@$Domaine" `
            -Name "$prenom $nom" `
            -GivenName $prenom `
            -Surname $nom `
            -DisplayName "$prenom $nom" `
            -EmailAddress $mail `
            -Title $fonction `
            -Department $service `
            -Path $ouService `
            -AccountPassword $mdpSecure `
            -Enabled $true `
            -ChangePasswordAtLogon $true

        # Ajouter au groupe du service
        $groupe = "GRP_$service"
        try {
            Add-ADGroupMember -Identity $groupe -Members $login
        } catch {
            Write-Log "Groupe $groupe introuvable pour $login" "ERREUR"
        }

        Write-Log "Utilisateur $login cree (mdp temporaire : $mdpClair)" "OK"
        $compteurOK++
    } catch {
        Write-Log "Erreur creation $login : $_" "ERREUR"
        $compteurErreur++
    }
}

Write-Log "Traitement termine : $compteurOK crees, $compteurSkip ignores, $compteurErreur erreurs"

Inventaire des machines

# Inventaire_Machines.ps1
# Collecte des informations materielles et logicielles des machines du domaine

param(
    [string]$FichierSortie = "C:\Rapports\inventaire_$(Get-Date -Format 'yyyyMMdd').csv"
)

$ordinateurs = Get-ADComputer -Filter {Enabled -eq $true} -Properties OperatingSystem | Select-Object -ExpandProperty Name

$resultats = @()

foreach ($pc in $ordinateurs) {
    Write-Host "Interrogation de $pc..."

    if (-not (Test-NetConnection -ComputerName $pc -InformationLevel Quiet -WarningAction SilentlyContinue)) {
        Write-Host "  $pc injoignable" -ForegroundColor Yellow
        $resultats += [PSCustomObject]@{
            Nom = $pc
            Statut = "Injoignable"
            OS = ""
            RAM_GB = ""
            CPU = ""
            DisqueC_GB = ""
            IP = ""
            DernierDemarrage = ""
        }
        continue
    }

    try {
        $os = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $pc
        $cpu = Get-CimInstance -ClassName Win32_Processor -ComputerName $pc | Select-Object -First 1
        $disque = Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $pc | Where-Object { $_.DeviceID -eq "C:" }
        $reseau = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -ComputerName $pc | Where-Object { $_.IPEnabled -eq $true } | Select-Object -First 1

        $resultats += [PSCustomObject]@{
            Nom = $pc
            Statut = "OK"
            OS = $os.Caption
            RAM_GB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
            CPU = $cpu.Name
            DisqueC_GB = [math]::Round($disque.FreeSpace / 1GB, 2)
            IP = ($reseau.IPAddress | Where-Object { $_ -match '^\d+\.\d+\.\d+\.\d+$' }) -join ", "
            DernierDemarrage = $os.LastBootUpTime
        }
    } catch {
        Write-Host "  Erreur sur $pc : $_" -ForegroundColor Red
        $resultats += [PSCustomObject]@{
            Nom = $pc
            Statut = "Erreur"
            OS = ""
            RAM_GB = ""
            CPU = ""
            DisqueC_GB = ""
            IP = ""
            DernierDemarrage = ""
        }
    }
}

$resultats | Export-Csv -Path $FichierSortie -NoTypeInformation -Delimiter ";" -Encoding UTF8
Write-Host "Inventaire exporte vers $FichierSortie ($($resultats.Count) machines)"

Rapport espace disque

# Rapport_Disque.ps1

param(
    [string[]]$Serveurs = @("SRV-01", "SRV-02", "SRV-03"),
    [int]$SeuilAlerte = 80,
    [string]$Rapport = "C:\Rapports\disque_$(Get-Date -Format 'yyyyMMdd').html"
)

$resultats = @()

foreach ($srv in $Serveurs) {
    if (-not (Test-NetConnection -ComputerName $srv -InformationLevel Quiet -WarningAction SilentlyContinue)) {
        continue
    }

    $disques = Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $srv -Filter "DriveType=3"

    foreach ($d in $disques) {
        $totalGB = [math]::Round($d.Size / 1GB, 2)
        $libreGB = [math]::Round($d.FreeSpace / 1GB, 2)
        $utilisePC = [math]::Round(100 - ($d.FreeSpace / $d.Size * 100), 1)

        $resultats += [PSCustomObject]@{
            Serveur = $srv
            Lecteur = $d.DeviceID
            TotalGB = $totalGB
            LibreGB = $libreGB
            UtilisePourcent = $utilisePC
            Alerte = if ($utilisePC -ge $SeuilAlerte) { "OUI" } else { "NON" }
        }
    }
}

# Generation du rapport HTML
$css = @"
<style>
    body { font-family: Arial, sans-serif; margin: 20px; }
    table { border-collapse: collapse; width: 100%; }
    th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
    th { background-color: #4472C4; color: white; }
    tr:nth-child(even) { background-color: #f2f2f2; }
    .alerte { background-color: #ff6b6b; color: white; font-weight: bold; }
</style>
"@

$html = $resultats | ConvertTo-Html -Title "Rapport Espace Disque" -Head $css -PreContent "<h1>Rapport Espace Disque - $(Get-Date -Format 'dd/MM/yyyy HH:mm')</h1>"
$html | Out-File $Rapport -Encoding UTF8

Write-Host "Rapport genere : $Rapport"

# Alertes par mail
$alertes = $resultats | Where-Object { $_.Alerte -eq "OUI" }
if ($alertes.Count -gt 0) {
    $corps = $alertes | Format-Table -AutoSize | Out-String
    # Send-MailMessage -To "admin@entreprise.local" -From "monitoring@entreprise.local" -Subject "[DISQUE] Alerte espace disque" -Body $corps -SmtpServer "smtp.entreprise.local"
}

Verification de services

# Verification_Services.ps1

param(
    [string]$FichierConfig = "C:\Scripts\services_critiques.json"
)

# Contenu attendu du JSON :
# [
#   { "Serveur": "SRV-DC01", "Services": ["NTDS", "DNS", "Netlogon", "W32Time"] },
#   { "Serveur": "SRV-WEB01", "Services": ["W3SVC", "WAS"] },
#   { "Serveur": "SRV-BDD01", "Services": ["MSSQLSERVER", "SQLSERVERAGENT"] }
# ]

$config = Get-Content $FichierConfig | ConvertFrom-Json
$problemes = @()

foreach ($item in $config) {
    $serveur = $item.Serveur

    if (-not (Test-NetConnection -ComputerName $serveur -InformationLevel Quiet -WarningAction SilentlyContinue)) {
        $problemes += [PSCustomObject]@{
            Serveur = $serveur
            Service = "N/A"
            Probleme = "Serveur injoignable"
            Action = "Aucune"
        }
        continue
    }

    foreach ($svc in $item.Services) {
        try {
            $etat = Get-Service -Name $svc -ComputerName $serveur -ErrorAction Stop

            if ($etat.Status -ne "Running") {
                Write-Host "[ARRETE] $serveur/$svc - Tentative de redemarrage..." -ForegroundColor Yellow

                try {
                    Get-Service -Name $svc -ComputerName $serveur | Start-Service -ErrorAction Stop
                    Write-Host "  Redemarrage reussi" -ForegroundColor Green
                    $problemes += [PSCustomObject]@{
                        Serveur = $serveur
                        Service = $svc
                        Probleme = "Etait arrete"
                        Action = "Redemarrage reussi"
                    }
                } catch {
                    Write-Host "  Echec du redemarrage" -ForegroundColor Red
                    $problemes += [PSCustomObject]@{
                        Serveur = $serveur
                        Service = $svc
                        Probleme = "Etait arrete"
                        Action = "Echec du redemarrage : $_"
                    }
                }
            } else {
                Write-Host "[OK] $serveur/$svc" -ForegroundColor Green
            }
        } catch {
            $problemes += [PSCustomObject]@{
                Serveur = $serveur
                Service = $svc
                Probleme = "Service introuvable"
                Action = "Verification manuelle requise"
            }
        }
    }
}

if ($problemes.Count -gt 0) {
    Write-Host "`nResume des problemes :" -ForegroundColor Cyan
    $problemes | Format-Table -AutoSize
}

Deploiement logiciel a distance

# Deploiement_Logiciel.ps1

param(
    [Parameter(Mandatory=$true)]
    [string]$Installeur,

    [string]$Arguments = "/S /quiet /norestart",
    [string[]]$Machines,
    [string]$FichierMachines
)

if ($FichierMachines -and (Test-Path $FichierMachines)) {
    $Machines = Get-Content $FichierMachines
}

if (-not $Machines -or $Machines.Count -eq 0) {
    Write-Host "Aucune machine cible specifiee"
    exit 1
}

$nomInstalleur = Split-Path $Installeur -Leaf
$partageTemp = "\\$env:COMPUTERNAME\DeploiementTemp"

# Copier l'installeur sur un partage accessible
if (-not (Test-Path "C:\DeploiementTemp")) {
    New-Item -Path "C:\DeploiementTemp" -ItemType Directory | Out-Null
    New-SmbShare -Name "DeploiementTemp" -Path "C:\DeploiementTemp" -ReadAccess "Tout le monde" | Out-Null
}
Copy-Item $Installeur -Destination "C:\DeploiementTemp\"

$resultats = @()

foreach ($machine in $Machines) {
    Write-Host "Deploiement sur $machine..."

    if (-not (Test-NetConnection -ComputerName $machine -InformationLevel Quiet -WarningAction SilentlyContinue)) {
        $resultats += [PSCustomObject]@{ Machine = $machine; Resultat = "Injoignable" }
        continue
    }

    try {
        $retour = Invoke-Command -ComputerName $machine -ScriptBlock {
            param($Partage, $Nom, $Args)
            $local = "C:\Temp\$Nom"

            if (-not (Test-Path "C:\Temp")) { New-Item -Path "C:\Temp" -ItemType Directory | Out-Null }
            Copy-Item "$Partage\$Nom" -Destination $local -Force

            $process = Start-Process -FilePath $local -ArgumentList $Args -Wait -PassThru
            Remove-Item $local -Force

            return $process.ExitCode
        } -ArgumentList $partageTemp, $nomInstalleur, $Arguments

        $status = if ($retour -eq 0) { "Succes" } else { "Echec (code $retour)" }
        $resultats += [PSCustomObject]@{ Machine = $machine; Resultat = $status }
        Write-Host "  $status" -ForegroundColor $(if ($retour -eq 0) { "Green" } else { "Red" })
    } catch {
        $resultats += [PSCustomObject]@{ Machine = $machine; Resultat = "Erreur : $_" }
    }
}

$resultats | Format-Table -AutoSize

2.13 Execution de scripts

Politique d'execution

# Verifier la politique actuelle
Get-ExecutionPolicy
Get-ExecutionPolicy -List     # toutes les portees

# Modifier la politique
Set-ExecutionPolicy RemoteSigned        # scripts locaux OK, distants signes
Set-ExecutionPolicy Unrestricted        # tout autorise (deconseille en production)
Set-ExecutionPolicy Restricted          # aucun script (par defaut)
Set-ExecutionPolicy Bypass              # aucune restriction
Set-ExecutionPolicy AllSigned           # tous les scripts doivent etre signes

# Portees (Scope)
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine

# Contourner ponctuellement
powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\monscript.ps1"
PolitiqueDescription
RestrictedAucun script autorise (defaut Windows client)
AllSignedScripts signes uniquement
RemoteSignedScripts locaux OK, distants doivent etre signes
UnrestrictedTout autorise, avertissement pour scripts distants
BypassAucune restriction, aucun avertissement

Executer un script

# Depuis PowerShell
.\monscript.ps1
C:\Scripts\monscript.ps1
& "C:\Scripts\mon script avec espaces.ps1"

# Avec des parametres
.\monscript.ps1 -Parametre1 "valeur" -Parametre2 42

# Depuis cmd.exe
powershell.exe -File "C:\Scripts\monscript.ps1"

# Editeurs
# ISE : Windows PowerShell ISE (integre a Windows, pour PS 5.1)
# VS Code : editeur recommande avec extension PowerShell

2.14 Taches planifiees Windows

Avec schtasks (ligne de commande)

REM Creer une tache quotidienne
schtasks /Create /TN "Sauvegarde" /TR "powershell.exe -File C:\Scripts\sauvegarde.ps1" /SC DAILY /ST 02:00 /RU SYSTEM

REM Creer une tache hebdomadaire (lundi)
schtasks /Create /TN "Rapport" /TR "powershell.exe -File C:\Scripts\rapport.ps1" /SC WEEKLY /D MON /ST 08:00

REM Lister les taches
schtasks /Query /TN "Sauvegarde" /V /FO LIST

REM Executer manuellement
schtasks /Run /TN "Sauvegarde"

REM Supprimer une tache
schtasks /Delete /TN "Sauvegarde" /F

REM Modifier une tache
schtasks /Change /TN "Sauvegarde" /ST 03:00

Avec PowerShell (Register-ScheduledTask)

# Creer une tache planifiee complete
$action = New-ScheduledTaskAction `
    -Execute "powershell.exe" `
    -Argument "-ExecutionPolicy Bypass -File C:\Scripts\sauvegarde.ps1" `
    -WorkingDirectory "C:\Scripts"

$declencheur = New-ScheduledTaskTrigger `
    -Daily `
    -At "02:00"

$parametres = New-ScheduledTaskSettingsSet `
    -StartWhenAvailable `
    -DontStopOnIdleEnd `
    -RestartCount 3 `
    -RestartInterval (New-TimeSpan -Minutes 5)

$principal = New-ScheduledTaskPrincipal `
    -UserId "SYSTEM" `
    -RunLevel Highest

Register-ScheduledTask `
    -TaskName "Sauvegarde_Quotidienne" `
    -Action $action `
    -Trigger $declencheur `
    -Settings $parametres `
    -Principal $principal `
    -Description "Sauvegarde quotidienne automatique"

# Declencheur hebdomadaire
$declencheurHebdo = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At "08:00"

# Declencheur au demarrage
$declencheurBoot = New-ScheduledTaskTrigger -AtStartup

# Declencheur a l'ouverture de session
$declencheurLogon = New-ScheduledTaskTrigger -AtLogOn

# Gerer les taches
Get-ScheduledTask -TaskName "Sauvegarde*"
Start-ScheduledTask -TaskName "Sauvegarde_Quotidienne"
Stop-ScheduledTask -TaskName "Sauvegarde_Quotidienne"
Disable-ScheduledTask -TaskName "Sauvegarde_Quotidienne"
Enable-ScheduledTask -TaskName "Sauvegarde_Quotidienne"
Unregister-ScheduledTask -TaskName "Sauvegarde_Quotidienne" -Confirm:$false

# Historique d'execution
Get-ScheduledTaskInfo -TaskName "Sauvegarde_Quotidienne"

2.15 Gestion a distance

WinRM (Windows Remote Management)

# Activer WinRM sur la machine cible
Enable-PSRemoting -Force

# Verifier la configuration WinRM
winrm get winrm/config
winrm quickconfig

# Ajouter des hotes de confiance (si hors domaine)
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "192.168.1.10,SRV-01"
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*"   # tous (deconseille)

# Tester la connexion
Test-WSMan -ComputerName "SRV-01"

Session interactive (Enter-PSSession)

# Ouvrir une session interactive
Enter-PSSession -ComputerName "SRV-01"
# Le prompt change : [SRV-01]: PS C:\Users\admin>
# Toutes les commandes s'executent sur la machine distante

# Quitter
Exit-PSSession

# Avec des identifiants specifiques
$cred = Get-Credential
Enter-PSSession -ComputerName "SRV-01" -Credential $cred

Execution a distance (Invoke-Command)

# Commande unique sur une machine
Invoke-Command -ComputerName "SRV-01" -ScriptBlock {
    Get-Service | Where-Object { $_.Status -eq "Running" } | Select-Object Name, Status
}

# Commande sur plusieurs machines en parallele
Invoke-Command -ComputerName "SRV-01", "SRV-02", "SRV-03" -ScriptBlock {
    [PSCustomObject]@{
        Machine = $env:COMPUTERNAME
        Uptime = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
        RAM_Libre = [math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB, 2)
    }
}

# Avec passage de variables locales
$nomService = "wuauserv"
Invoke-Command -ComputerName "SRV-01" -ScriptBlock {
    param($svc)
    Restart-Service -Name $svc
} -ArgumentList $nomService

# Avec $using: (alternative)
Invoke-Command -ComputerName "SRV-01" -ScriptBlock {
    Restart-Service -Name $using:nomService
}

# Executer un script local sur une machine distante
Invoke-Command -ComputerName "SRV-01" -FilePath "C:\Scripts\maintenance.ps1"

# Sessions persistantes (reutilisables)
$session = New-PSSession -ComputerName "SRV-01"
Invoke-Command -Session $session -ScriptBlock { Get-Process }
Invoke-Command -Session $session -ScriptBlock { Get-Service }
Remove-PSSession $session

# Copier des fichiers via session
$session = New-PSSession -ComputerName "SRV-01"
Copy-Item -Path "C:\local\fichier.txt" -Destination "C:\distant\" -ToSession $session
Copy-Item -Path "C:\distant\rapport.txt" -Destination "C:\local\" -FromSession $session
Remove-PSSession $session

2.16 Gestion des erreurs

# Try/Catch/Finally
try {
    $contenu = Get-Content "C:\fichier_inexistant.txt" -ErrorAction Stop
    Write-Host "Fichier lu avec succes"
} catch [System.IO.FileNotFoundException] {
    Write-Host "Fichier non trouve : $($_.Exception.Message)"
} catch {
    Write-Host "Erreur inattendue : $($_.Exception.Message)"
} finally {
    Write-Host "Bloc finally execute dans tous les cas"
}

# ErrorAction : controle du comportement en cas d'erreur
Get-Service "ServiceInexistant" -ErrorAction SilentlyContinue  # pas d'erreur affichee
Get-Service "ServiceInexistant" -ErrorAction Stop               # leve une exception
Get-Service "ServiceInexistant" -ErrorAction Continue           # affiche l'erreur et continue

# Variable $Error
$Error[0]                # derniere erreur
$Error.Count             # nombre d'erreurs dans la session
$Error.Clear()           # vider le journal d'erreurs

# Preference globale
$ErrorActionPreference = "Stop"    # toute erreur devient exception

Partie 3 — Exercices corriges

Exercice 1 — Bash : Arguments et conditions

Ecrire un script verifier_fichier.sh qui prend un chemin en argument et affiche si c'est un fichier, un repertoire, ou s'il n'existe pas. Afficher egalement ses permissions.

Correction :

#!/bin/bash
# verifier_fichier.sh

if [ $# -ne 1 ]; then
    echo "Usage: $0 <chemin>"
    exit 1
fi

CHEMIN="$1"

if [ ! -e "$CHEMIN" ]; then
    echo "Le chemin '$CHEMIN' n'existe pas"
    exit 1
fi

if [ -f "$CHEMIN" ]; then
    echo "'$CHEMIN' est un fichier regulier"
    echo "Taille : $(du -h "$CHEMIN" | cut -f1)"
elif [ -d "$CHEMIN" ]; then
    echo "'$CHEMIN' est un repertoire"
    echo "Contenu : $(ls -1 "$CHEMIN" | wc -l) elements"
elif [ -L "$CHEMIN" ]; then
    echo "'$CHEMIN' est un lien symbolique vers $(readlink "$CHEMIN")"
fi

echo "Permissions : $(ls -ld "$CHEMIN" | awk '{print $1}')"
echo "Proprietaire : $(ls -ld "$CHEMIN" | awk '{print $3":"$4}')"

[ -r "$CHEMIN" ] && echo "  Lisible : oui" || echo "  Lisible : non"
[ -w "$CHEMIN" ] && echo "  Ecriture : oui" || echo "  Ecriture : non"
[ -x "$CHEMIN" ] && echo "  Executable : oui" || echo "  Executable : non"

Exercice 2 — Bash : Boucle et traitement de texte

Ecrire un script qui lit /etc/passwd et affiche les utilisateurs ayant un UID superieur ou egal a 1000 avec leur shell.

Correction :

#!/bin/bash
# utilisateurs_systeme.sh

echo "=== Utilisateurs avec UID >= 1000 ==="
printf "%-20s %-8s %s\n" "LOGIN" "UID" "SHELL"
echo "----------------------------------------"

while IFS=':' read -r login _ uid _ _ _ shell; do
    if [ "$uid" -ge 1000 ] 2>/dev/null; then
        printf "%-20s %-8s %s\n" "$login" "$uid" "$shell"
    fi
done < /etc/passwd

echo ""
echo "Total : $(awk -F: '$3 >= 1000 {count++} END {print count}' /etc/passwd) utilisateur(s)"

Exercice 3 — Bash : Sauvegarde conditionnelle

Ecrire un script de sauvegarde qui verifie l'espace disque avant de proceder. Si l'espace libre est inferieur a 1 Go, la sauvegarde est annulee.

Correction :

#!/bin/bash
# sauvegarde_conditionnelle.sh

SOURCE="${1:-/home}"
DESTINATION="${2:-/backup}"
SEUIL_GO=1
DATE=$(date +%Y%m%d_%H%M%S)
ARCHIVE="$DESTINATION/backup_${DATE}.tar.gz"

# Verifier que la source existe
if [ ! -d "$SOURCE" ]; then
    echo "ERREUR : le repertoire source '$SOURCE' n'existe pas"
    exit 1
fi

# Creer le repertoire de destination si necessaire
mkdir -p "$DESTINATION"

# Verifier l'espace disque disponible (en Ko)
espace_ko=$(df -k "$DESTINATION" | tail -1 | awk '{print $4}')
espace_go=$((espace_ko / 1048576))

if [ "$espace_go" -lt "$SEUIL_GO" ]; then
    echo "ERREUR : espace insuffisant (${espace_go} Go libre, minimum ${SEUIL_GO} Go)"
    exit 1
fi

echo "Espace disponible : ${espace_go} Go - Sauvegarde en cours..."

# Calculer la taille de la source
taille_source=$(du -sh "$SOURCE" | cut -f1)
echo "Taille de la source : $taille_source"

# Effectuer la sauvegarde
if tar -czf "$ARCHIVE" "$SOURCE" 2>/dev/null; then
    taille_archive=$(du -h "$ARCHIVE" | cut -f1)
    echo "Sauvegarde reussie : $ARCHIVE ($taille_archive)"
else
    echo "ERREUR : la sauvegarde a echoue"
    [ -f "$ARCHIVE" ] && rm -f "$ARCHIVE"
    exit 1
fi

Exercice 4 — Bash : Analyse de logs avec grep, awk, sort

Analyser un fichier de log Apache pour trouver les 10 adresses IP les plus frequentes et les codes HTTP les plus courants.

Correction :

#!/bin/bash
# analyse_logs.sh

LOG="${1:-/var/log/apache2/access.log}"

if [ ! -f "$LOG" ]; then
    echo "ERREUR : fichier $LOG introuvable"
    exit 1
fi

total=$(wc -l < "$LOG")
echo "=== Analyse de $LOG ($total requetes) ==="

echo ""
echo "--- Top 10 adresses IP ---"
awk '{print $1}' "$LOG" | sort | uniq -c | sort -rn | head -10 | \
    awk '{printf "  %6d requetes - %s\n", $1, $2}'

echo ""
echo "--- Repartition des codes HTTP ---"
awk '{print $9}' "$LOG" | grep -E '^[0-9]{3}$' | sort | uniq -c | sort -rn | \
    awk -v total="$total" '{printf "  Code %s : %6d (%5.1f%%)\n", $2, $1, ($1/total)*100}'

echo ""
echo "--- Top 10 pages demandees ---"
awk '{print $7}' "$LOG" | sort | uniq -c | sort -rn | head -10 | \
    awk '{printf "  %6d - %s\n", $1, $2}'

echo ""
echo "--- Requetes par heure ---"
awk -F'[\\[/: ]' '{print $5}' "$LOG" | sort | uniq -c | \
    awk '{printf "  %sh : %6d requetes\n", $2, $1}'

Exercice 5 — Bash : Fonction et menu interactif

Ecrire un script avec un menu interactif pour gerer des utilisateurs (ajouter, supprimer, lister, verrouiller).

Correction :

#!/bin/bash
# gestion_utilisateurs.sh
# Necessite les droits root

if [ "$(id -u)" -ne 0 ]; then
    echo "Ce script doit etre execute en tant que root"
    exit 1
fi

ajouter_utilisateur() {
    read -p "Login : " login
    read -p "Nom complet : " nom
    read -p "Groupe principal : " groupe

    if id "$login" &>/dev/null; then
        echo "L'utilisateur $login existe deja"
        return 1
    fi

    if ! getent group "$groupe" &>/dev/null; then
        groupadd "$groupe"
        echo "Groupe $groupe cree"
    fi

    useradd -m -g "$groupe" -c "$nom" -s /bin/bash "$login"
    if [ $? -eq 0 ]; then
        passwd "$login"
        echo "Utilisateur $login cree avec succes"
    else
        echo "Erreur lors de la creation"
    fi
}

supprimer_utilisateur() {
    read -p "Login a supprimer : " login
    if ! id "$login" &>/dev/null; then
        echo "L'utilisateur $login n'existe pas"
        return 1
    fi

    read -p "Supprimer le repertoire personnel ? (o/n) : " choix
    if [ "$choix" = "o" ]; then
        userdel -r "$login"
    else
        userdel "$login"
    fi
    echo "Utilisateur $login supprime"
}

lister_utilisateurs() {
    echo ""
    printf "%-15s %-6s %-15s %s\n" "LOGIN" "UID" "GROUPE" "NOM"
    echo "---------------------------------------------------"
    awk -F: '$3 >= 1000 && $3 < 65534 {
        cmd = "id -gn "$1
        cmd | getline grp
        close(cmd)
        printf "%-15s %-6s %-15s %s\n", $1, $3, grp, $5
    }' /etc/passwd
}

verrouiller_utilisateur() {
    read -p "Login a verrouiller/deverrouiller : " login
    if ! id "$login" &>/dev/null; then
        echo "L'utilisateur $login n'existe pas"
        return 1
    fi

    statut=$(passwd -S "$login" | awk '{print $2}')
    if [ "$statut" = "L" ]; then
        read -p "$login est verrouille. Deverrouiller ? (o/n) : " choix
        [ "$choix" = "o" ] && usermod -U "$login" && echo "Compte deverrouille"
    else
        usermod -L "$login"
        echo "Compte $login verrouille"
    fi
}

while true; do
    echo ""
    echo "========================================="
    echo "   GESTION DES UTILISATEURS"
    echo "========================================="
    echo "  1. Ajouter un utilisateur"
    echo "  2. Supprimer un utilisateur"
    echo "  3. Lister les utilisateurs"
    echo "  4. Verrouiller/Deverrouiller un compte"
    echo "  5. Quitter"
    echo "========================================="
    read -p "Choix : " choix

    case "$choix" in
        1) ajouter_utilisateur ;;
        2) supprimer_utilisateur ;;
        3) lister_utilisateurs ;;
        4) verrouiller_utilisateur ;;
        5) echo "Au revoir"; exit 0 ;;
        *) echo "Choix invalide" ;;
    esac
done

Exercice 6 — PowerShell : Variables et operateurs

Ecrire un script qui demande le nom et l'age de l'utilisateur, puis affiche un message personnalise selon la tranche d'age.

Correction :

# Exercice6_Age.ps1

$nom = Read-Host "Entrez votre nom"
[int]$age = Read-Host "Entrez votre age"

$anneeNaissance = (Get-Date).Year - $age

$message = switch ($true) {
    ($age -lt 0)   { "Age invalide" }
    ($age -lt 18)  { "Mineur" }
    ($age -lt 30)  { "Jeune actif" }
    ($age -lt 50)  { "Actif" }
    ($age -lt 65)  { "Senior" }
    default        { "Retraite" }
}

Write-Host ""
Write-Host "Nom             : $nom"
Write-Host "Age             : $age ans"
Write-Host "Annee naissance : ~$anneeNaissance"
Write-Host "Categorie       : $message"

# Verification supplementaire
if ($age -ge 18) {
    Write-Host "Statut          : Majeur"
} else {
    Write-Host "Statut          : Mineur"
    Write-Host "Majorite dans   : $(18 - $age) an(s)"
}

Exercice 7 — PowerShell : Boucles et tableaux

Ecrire un script qui verifie la connectivite reseau d'une liste de serveurs et genere un rapport.

Correction :

# Exercice7_Ping.ps1

$serveurs = @(
    @{ Nom = "SRV-DC01";   IP = "192.168.1.10" },
    @{ Nom = "SRV-WEB01";  IP = "192.168.1.20" },
    @{ Nom = "SRV-BDD01";  IP = "192.168.1.30" },
    @{ Nom = "SRV-MAIL01"; IP = "192.168.1.40" },
    @{ Nom = "Google DNS";  IP = "8.8.8.8" }
)

$resultats = @()

foreach ($srv in $serveurs) {
    Write-Host "Test de $($srv.Nom) ($($srv.IP))..." -NoNewline

    $ping = Test-NetConnection -ComputerName $srv.IP -WarningAction SilentlyContinue

    $etat = if ($ping.PingSucceeded) { "Joignable" } else { "Injoignable" }
    $couleur = if ($ping.PingSucceeded) { "Green" } else { "Red" }

    Write-Host " $etat" -ForegroundColor $couleur

    $resultats += [PSCustomObject]@{
        Nom     = $srv.Nom
        IP      = $srv.IP
        Statut  = $etat
        Latence = if ($ping.PingSucceeded) { "$($ping.PingReplyDetails.RoundtripTime) ms" } else { "N/A" }
    }
}

Write-Host ""
Write-Host "=== Rapport de connectivite ===" -ForegroundColor Cyan
$resultats | Format-Table -AutoSize

$joignables = ($resultats | Where-Object { $_.Statut -eq "Joignable" }).Count
$total = $resultats.Count
Write-Host "Resultat : $joignables/$total serveurs joignables"

Exercice 8 — PowerShell : Fonctions et gestion de fichiers

Ecrire une fonction qui recherche les fichiers volumineux (> seuil parametrable) dans un repertoire et propose leur suppression.

Correction :

# Exercice8_FichiersVolumineux.ps1

function Find-LargeFiles {
    param(
        [Parameter(Mandatory=$true)]
        [string]$Chemin,

        [int]$SeuilMB = 100,

        [switch]$Supprimer
    )

    if (-not (Test-Path $Chemin)) {
        Write-Host "Le chemin $Chemin n'existe pas" -ForegroundColor Red
        return
    }

    Write-Host "Recherche des fichiers > ${SeuilMB} Mo dans $Chemin..."

    $fichiers = Get-ChildItem -Path $Chemin -Recurse -File -ErrorAction SilentlyContinue |
        Where-Object { $_.Length -gt ($SeuilMB * 1MB) } |
        Sort-Object Length -Descending

    if ($fichiers.Count -eq 0) {
        Write-Host "Aucun fichier superieur a ${SeuilMB} Mo trouve"
        return
    }

    $espaceTotal = ($fichiers | Measure-Object Length -Sum).Sum

    Write-Host "$($fichiers.Count) fichier(s) trouve(s), espace total : $([math]::Round($espaceTotal / 1MB, 2)) Mo"
    Write-Host ""

    $resultats = $fichiers | ForEach-Object {
        [PSCustomObject]@{
            Nom     = $_.Name
            Chemin  = $_.DirectoryName
            TailleMB = [math]::Round($_.Length / 1MB, 2)
            Modifie = $_.LastWriteTime.ToString("dd/MM/yyyy HH:mm")
        }
    }

    $resultats | Format-Table -AutoSize

    if ($Supprimer) {
        foreach ($f in $fichiers) {
            $reponse = Read-Host "Supprimer $($f.FullName) ($([math]::Round($f.Length / 1MB, 2)) Mo) ? (o/n)"
            if ($reponse -eq "o") {
                Remove-Item $f.FullName -Force
                Write-Host "  Supprime" -ForegroundColor Green
            } else {
                Write-Host "  Ignore" -ForegroundColor Yellow
            }
        }
    }
}

# Utilisation
Find-LargeFiles -Chemin "C:\Users" -SeuilMB 50
# Find-LargeFiles -Chemin "C:\Temp" -SeuilMB 10 -Supprimer

Exercice 9 — PowerShell : Pipeline et objets

Ecrire un script qui genere un rapport HTML des processus consommant le plus de memoire et de CPU.

Correction :

# Exercice9_RapportProcessus.ps1

param(
    [int]$Top = 15,
    [string]$Sortie = "C:\Rapports\processus_$(Get-Date -Format 'yyyyMMdd_HHmm').html"
)

$css = @"
<style>
    body { font-family: Segoe UI, Arial; margin: 20px; background: #f5f5f5; }
    h1 { color: #2c3e50; }
    h2 { color: #34495e; border-bottom: 2px solid #3498db; padding-bottom: 5px; }
    table { border-collapse: collapse; width: 100%; margin-bottom: 30px; background: white; }
    th { background: #3498db; color: white; padding: 10px; }
    td { border: 1px solid #ddd; padding: 8px; }
    tr:nth-child(even) { background: #ecf0f1; }
    .info { color: #7f8c8d; }
</style>
"@

# Top processus par RAM
$topRAM = Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First $Top |
    Select-Object Name, Id,
        @{N='RAM_MB'; E={[math]::Round($_.WorkingSet64 / 1MB, 2)}},
        @{N='CPU_Sec'; E={[math]::Round($_.CPU, 2)}},
        StartTime

# Top processus par CPU
$topCPU = Get-Process | Where-Object { $_.CPU -gt 0 } | Sort-Object CPU -Descending | Select-Object -First $Top |
    Select-Object Name, Id,
        @{N='CPU_Sec'; E={[math]::Round($_.CPU, 2)}},
        @{N='RAM_MB'; E={[math]::Round($_.WorkingSet64 / 1MB, 2)}},
        StartTime

# Statistiques generales
$totalProcessus = (Get-Process).Count
$ramTotale = [math]::Round((Get-CimInstance Win32_OperatingSystem).TotalVisibleMemorySize / 1MB, 2)
$ramLibre = [math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB, 2)

$preContenu = @"
<h1>Rapport des processus - $env:COMPUTERNAME</h1>
<p class='info'>Genere le $(Get-Date -Format 'dd/MM/yyyy a HH:mm:ss')</p>
<p>Processus actifs : $totalProcessus | RAM totale : ${ramTotale} Go | RAM libre : ${ramLibre} Go</p>
<h2>Top $Top processus par memoire (RAM)</h2>
"@

$htmlRAM = $topRAM | ConvertTo-Html -Fragment
$htmlCPU = $topCPU | ConvertTo-Html -Fragment

$htmlComplet = ConvertTo-Html -Title "Rapport Processus" -Head $css `
    -Body "$preContenu $htmlRAM <h2>Top $Top processus par CPU</h2> $htmlCPU"

# Creer le dossier si necessaire
$dossier = Split-Path $Sortie -Parent
if (-not (Test-Path $dossier)) { New-Item -Path $dossier -ItemType Directory | Out-Null }

$htmlComplet | Out-File $Sortie -Encoding UTF8
Write-Host "Rapport genere : $Sortie"

Exercice 10 — PowerShell : Active Directory en masse

Ecrire un script qui desactive tous les comptes AD inactifs depuis plus de 90 jours et genere un rapport CSV.

Correction :

# Exercice10_ComptesInactifs.ps1

param(
    [int]$JoursInactivite = 90,
    [string]$RapportCSV = "C:\Rapports\comptes_inactifs_$(Get-Date -Format 'yyyyMMdd').csv",
    [switch]$Desactiver
)

Import-Module ActiveDirectory

$dateLimite = (Get-Date).AddDays(-$JoursInactivite)

# Rechercher les comptes inactifs
$comptesInactifs = Get-ADUser -Filter {
    Enabled -eq $true -and LastLogonDate -lt $dateLimite
} -Properties LastLogonDate, Department, Title, Manager, WhenCreated |
    Select-Object SamAccountName, Name, Department, Title,
        @{N='DernierLogin'; E={$_.LastLogonDate}},
        @{N='JoursInactif'; E={(New-TimeSpan -Start $_.LastLogonDate -End (Get-Date)).Days}},
        @{N='CreeLe'; E={$_.WhenCreated}},
        Enabled

Write-Host "Comptes inactifs depuis plus de $JoursInactivite jours : $($comptesInactifs.Count)"

if ($comptesInactifs.Count -eq 0) {
    Write-Host "Aucun compte inactif trouve"
    exit 0
}

# Afficher le resume
$comptesInactifs | Sort-Object JoursInactif -Descending | Format-Table SamAccountName, Name, Department, DernierLogin, JoursInactif -AutoSize

# Desactiver si demande
if ($Desactiver) {
    Write-Host ""
    Write-Host "Desactivation des comptes..." -ForegroundColor Yellow

    foreach ($compte in $comptesInactifs) {
        try {
            Disable-ADAccount -Identity $compte.SamAccountName
            Set-ADUser -Identity $compte.SamAccountName -Description "Desactive le $(Get-Date -Format 'dd/MM/yyyy') - Inactivite $($compte.JoursInactif) jours"
            Write-Host "  [OK] $($compte.SamAccountName) desactive" -ForegroundColor Green
        } catch {
            Write-Host "  [ERREUR] $($compte.SamAccountName) : $_" -ForegroundColor Red
        }
    }
}

# Export CSV
$comptesInactifs | Export-Csv -Path $RapportCSV -NoTypeInformation -Delimiter ";" -Encoding UTF8
Write-Host "Rapport exporte : $RapportCSV"

Exercice 11 — PowerShell : Gestion a distance

Ecrire un script qui collecte les informations systeme de plusieurs serveurs distants en parallele.

Correction :

# Exercice11_InfoDistantes.ps1

param(
    [string[]]$Serveurs = @("SRV-01", "SRV-02", "SRV-03")
)

$resultats = Invoke-Command -ComputerName $Serveurs -ScriptBlock {
    $os = Get-CimInstance Win32_OperatingSystem
    $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
    $disques = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3"
    $reseau = Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled }
    $services = Get-Service | Where-Object { $_.StartType -eq "Automatic" -and $_.Status -ne "Running" }

    [PSCustomObject]@{
        Hostname          = $env:COMPUTERNAME
        OS                = $os.Caption
        Version           = $os.Version
        Uptime            = (New-TimeSpan -Start $os.LastBootUpTime -End (Get-Date)).ToString("dd\.hh\:mm\:ss")
        CPU               = $cpu.Name.Trim()
        RAM_Total_GB      = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
        RAM_Libre_GB      = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
        Disques           = ($disques | ForEach-Object {
            "$($_.DeviceID) $([math]::Round($_.FreeSpace/1GB,1))/$([math]::Round($_.Size/1GB,1)) Go"
        }) -join " | "
        IP                = ($reseau | ForEach-Object { ($_.IPAddress | Where-Object { $_ -match '^\d+\.' }) }) -join ", "
        ServicesArretes   = ($services | ForEach-Object { $_.Name }) -join ", "
    }
} -ErrorAction SilentlyContinue -ErrorVariable erreurs

# Afficher les resultats
$resultats | Select-Object Hostname, OS, Uptime, RAM_Total_GB, RAM_Libre_GB, Disques | Format-Table -AutoSize

# Afficher les services arretes qui devraient tourner
Write-Host "--- Services automatiques arretes ---"
$resultats | Where-Object { $_.ServicesArretes -ne "" } | ForEach-Object {
    Write-Host "$($_.Hostname) : $($_.ServicesArretes)" -ForegroundColor Yellow
}

# Afficher les erreurs de connexion
foreach ($err in $erreurs) {
    Write-Host "Erreur : $($err.TargetObject) - $($err.Exception.Message)" -ForegroundColor Red
}

Exercice 12 — PowerShell : Taches planifiees

Ecrire un script qui cree un ensemble de taches planifiees pour la maintenance automatique d'un serveur.

Correction :

# Exercice12_TachesPlanifiees.ps1

param(
    [string]$CheminScripts = "C:\Scripts\Maintenance"
)

# Creer le repertoire de scripts
if (-not (Test-Path $CheminScripts)) {
    New-Item -Path $CheminScripts -ItemType Directory | Out-Null
}

# Liste des taches a creer
$taches = @(
    @{
        Nom          = "Maintenance_Sauvegarde_Quotidienne"
        Description  = "Sauvegarde quotidienne des donnees"
        Script       = "sauvegarde.ps1"
        Declencheur  = "Daily"
        Heure        = "02:00"
    },
    @{
        Nom          = "Maintenance_Nettoyage_Temp"
        Description  = "Nettoyage des fichiers temporaires"
        Script       = "nettoyage_temp.ps1"
        Declencheur  = "Daily"
        Heure        = "03:00"
    },
    @{
        Nom          = "Maintenance_Rapport_Hebdo"
        Description  = "Rapport hebdomadaire du systeme"
        Script       = "rapport_hebdo.ps1"
        Declencheur  = "Weekly"
        Heure        = "08:00"
        Jour         = "Monday"
    },
    @{
        Nom          = "Maintenance_Verification_Services"
        Description  = "Verification des services critiques toutes les 15 minutes"
        Script       = "verif_services.ps1"
        Declencheur  = "Repetition"
        Intervalle   = 15
    }
)

foreach ($t in $taches) {
    # Verifier si la tache existe deja
    $existante = Get-ScheduledTask -TaskName $t.Nom -ErrorAction SilentlyContinue
    if ($existante) {
        Write-Host "[SKIP] $($t.Nom) existe deja" -ForegroundColor Yellow
        continue
    }

    # Action
    $action = New-ScheduledTaskAction `
        -Execute "powershell.exe" `
        -Argument "-ExecutionPolicy Bypass -File `"$CheminScripts\$($t.Script)`"" `
        -WorkingDirectory $CheminScripts

    # Declencheur
    switch ($t.Declencheur) {
        "Daily" {
            $trigger = New-ScheduledTaskTrigger -Daily -At $t.Heure
        }
        "Weekly" {
            $trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek $t.Jour -At $t.Heure
        }
        "Repetition" {
            $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
                -RepetitionInterval (New-TimeSpan -Minutes $t.Intervalle) `
                -RepetitionDuration (New-TimeSpan -Days 365)
        }
    }

    # Parametres
    $settings = New-ScheduledTaskSettingsSet `
        -StartWhenAvailable `
        -DontStopOnIdleEnd `
        -RestartCount 3 `
        -RestartInterval (New-TimeSpan -Minutes 1)

    # Enregistrer la tache
    try {
        Register-ScheduledTask `
            -TaskName $t.Nom `
            -Action $action `
            -Trigger $trigger `
            -Settings $settings `
            -User "SYSTEM" `
            -RunLevel Highest `
            -Description $t.Description

        Write-Host "[OK] $($t.Nom) creee" -ForegroundColor Green
    } catch {
        Write-Host "[ERREUR] $($t.Nom) : $_" -ForegroundColor Red
    }
}

# Lister les taches creees
Write-Host ""
Write-Host "=== Taches de maintenance planifiees ==="
Get-ScheduledTask -TaskName "Maintenance_*" | Format-Table TaskName, State, @{N='Prochain';E={(Get-ScheduledTaskInfo -InputObject $_).NextRunTime}} -AutoSize

Exercice 13 — Bash : Surveillance avec alerte par mail

Ecrire un script Bash qui surveille les connexions SSH echouees et envoie une alerte si un seuil est depasse.

Correction :

#!/bin/bash
# surveillance_ssh.sh

SEUIL=5
PERIODE=60   # minutes
LOG="/var/log/auth.log"
ADMIN="admin@exemple.fr"
FICHIER_ETAT="/tmp/ssh_surveillance.state"

# Extraire les tentatives echouees dans la derniere heure
depuis=$(date -d "-${PERIODE} minutes" '+%b %d %H:%M')

# Compter les echecs par IP
declare -A echecs

while read -r ligne; do
    ip=$(echo "$ligne" | grep -oP 'from \K[\d.]+')
    if [[ -n "$ip" ]]; then
        ((echecs[$ip]++))
    fi
done < <(grep "Failed password" "$LOG" | awk -v date="$depuis" '$0 >= date')

# Verifier les seuils
alerte=0
rapport=""

for ip in "${!echecs[@]}"; do
    if [[ ${echecs[$ip]} -ge $SEUIL ]]; then
        rapport+="  $ip : ${echecs[$ip]} tentatives echouees\n"
        alerte=1
    fi
done

if [[ $alerte -eq 1 ]]; then
    message="ALERTE SSH sur $(hostname) le $(date)\n\n"
    message+="Les adresses IP suivantes ont depasse le seuil de $SEUIL tentatives :\n\n"
    message+="$rapport\n"
    message+="Action recommandee : verifier et eventuellement bloquer ces adresses.\n"

    echo -e "$message" | mail -s "[SECURITE] Tentatives SSH suspectes sur $(hostname)" "$ADMIN"
    echo "$(date) - Alerte envoyee" >> "$FICHIER_ETAT"
fi

# Afficher le resume
echo "=== Surveillance SSH - $(date) ==="
echo "Periode analysee : dernieres $PERIODE minutes"
for ip in "${!echecs[@]}"; do
    indicateur=""
    [[ ${echecs[$ip]} -ge $SEUIL ]] && indicateur=" [ALERTE]"
    echo "  $ip : ${echecs[$ip]} echec(s)$indicateur"
done | sort -t: -k2 -rn

Exercice 14 — Mixte : Script de deploiement complet

Ecrire un script Bash qui deploie une application web (copie des fichiers, configuration Apache, redemarrage du service, verification).

Correction :

#!/bin/bash
# deploiement_web.sh

set -euo pipefail

# Configuration
APP_NOM="monapp"
APP_SRC="/opt/releases/${APP_NOM}"
APP_DEST="/var/www/${APP_NOM}"
VHOST_CONF="/etc/apache2/sites-available/${APP_NOM}.conf"
DOMAINE="${APP_NOM}.entreprise.local"
PORT=80
LOG="/var/log/deploiement_${APP_NOM}_$(date +%Y%m%d_%H%M%S).log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG"
}

erreur() {
    log "ERREUR : $1"
    exit 1
}

verifier_prerequis() {
    log "Verification des prerequis..."

    [ "$(id -u)" -ne 0 ] && erreur "Ce script doit etre execute en tant que root"
    [ ! -d "$APP_SRC" ] && erreur "Sources introuvables : $APP_SRC"

    if ! command -v apache2 &>/dev/null; then
        erreur "Apache2 n'est pas installe"
    fi

    log "Prerequis OK"
}

sauvegarder_ancienne_version() {
    if [ -d "$APP_DEST" ]; then
        local backup="${APP_DEST}_backup_$(date +%Y%m%d_%H%M%S)"
        log "Sauvegarde de l'ancienne version vers $backup"
        cp -a "$APP_DEST" "$backup"
    fi
}

deployer_fichiers() {
    log "Deploiement des fichiers..."

    mkdir -p "$APP_DEST"
    rsync -av --delete "$APP_SRC/" "$APP_DEST/" >> "$LOG" 2>&1

    chown -R www-data:www-data "$APP_DEST"
    chmod -R 755 "$APP_DEST"

    log "Fichiers deployes"
}

configurer_vhost() {
    log "Configuration du VirtualHost..."

    cat > "$VHOST_CONF" <<VHOST
<VirtualHost *:${PORT}>
    ServerName ${DOMAINE}
    DocumentRoot ${APP_DEST}

    <Directory ${APP_DEST}>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog \${APACHE_LOG_DIR}/${APP_NOM}_error.log
    CustomLog \${APACHE_LOG_DIR}/${APP_NOM}_access.log combined
</VirtualHost>
VHOST

    a2ensite "${APP_NOM}.conf" >> "$LOG" 2>&1
    log "VirtualHost configure"
}

redemarrer_apache() {
    log "Verification de la configuration Apache..."
    if ! apache2ctl configtest >> "$LOG" 2>&1; then
        erreur "Configuration Apache invalide"
    fi

    log "Redemarrage d'Apache..."
    systemctl restart apache2

    if systemctl is-active --quiet apache2; then
        log "Apache redemarre avec succes"
    else
        erreur "Apache n'a pas redemarre correctement"
    fi
}

verifier_deploiement() {
    log "Verification du deploiement..."

    sleep 2

    code_http=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${PORT}/" -H "Host: ${DOMAINE}")

    if [ "$code_http" -eq 200 ] || [ "$code_http" -eq 301 ] || [ "$code_http" -eq 302 ]; then
        log "Verification OK (HTTP $code_http)"
    else
        log "ATTENTION : code HTTP $code_http (attendu : 200)"
    fi
}

# Execution
log "=== Debut du deploiement de $APP_NOM ==="
verifier_prerequis
sauvegarder_ancienne_version
deployer_fichiers
configurer_vhost
redemarrer_apache
verifier_deploiement
log "=== Deploiement termine avec succes ==="

Exercice 15 — Mixte : Correspondance Bash / PowerShell

Pour chaque tache ci-dessous, ecrire la commande equivalente en Bash et en PowerShell.

Correction :

1. Lister les fichiers de plus de 100 Mo dans un repertoire :

# Bash
find /var/log -type f -size +100M -exec ls -lh {} \;
# PowerShell
Get-ChildItem C:\Logs -Recurse -File | Where-Object { $_.Length -gt 100MB } | Select-Object FullName, @{N='TailleMB';E={[math]::Round($_.Length/1MB,2)}}

2. Compter les lignes contenant "error" dans un fichier :

# Bash
grep -ci "error" /var/log/syslog
# PowerShell
(Get-Content C:\Logs\app.log | Select-String -Pattern "error").Count

3. Obtenir l'espace disque libre :

# Bash
df -h / | awk 'NR==2 {print $4}'
# PowerShell
[math]::Round((Get-PSDrive C).Free / 1GB, 2)
# ou
Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" | Select-Object @{N='LibreGB';E={[math]::Round($_.FreeSpace/1GB,2)}}

4. Creer 10 fichiers numerotes :

# Bash
for i in $(seq 1 10); do
    touch "fichier_${i}.txt"
done
# PowerShell
1..10 | ForEach-Object { New-Item -Path "fichier_$_.txt" -ItemType File }

5. Lister les 5 processus consommant le plus de memoire :

# Bash
ps aux --sort=-%mem | head -6
# PowerShell
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 5 Name, @{N='RAM_MB';E={[math]::Round($_.WorkingSet64/1MB,2)}}

6. Rechercher un utilisateur :

# Bash
grep "^jdupont:" /etc/passwd
# ou
getent passwd jdupont
# PowerShell
Get-ADUser -Identity "jdupont" -Properties *
# ou (local)
Get-LocalUser -Name "jdupont"

7. Redemarrer un service :

# Bash
sudo systemctl restart apache2
systemctl status apache2
# PowerShell
Restart-Service -Name "W3SVC"
Get-Service -Name "W3SVC"

8. Planifier une tache :

# Bash (crontab -e)
0 2 * * * /opt/scripts/sauvegarde.sh
# PowerShell
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File C:\Scripts\sauvegarde.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"
Register-ScheduledTask -TaskName "Sauvegarde" -Action $action -Trigger $trigger -User "SYSTEM"

Exercice 16 — PowerShell : Gestion des erreurs et journalisation

Ecrire un script robuste qui effectue une serie d'operations d'administration avec gestion complete des erreurs et journalisation.

Correction :

# Exercice16_MaintenanceRobuste.ps1

param(
    [string]$FichierLog = "C:\Logs\maintenance_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
)

$ErrorActionPreference = "Stop"

# Fonction de log
function Write-Log {
    param(
        [string]$Message,
        [ValidateSet("INFO","OK","WARN","ERROR")]
        [string]$Niveau = "INFO"
    )
    $horodatage = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $ligne = "[$horodatage] [$Niveau] $Message"
    Add-Content -Path $FichierLog -Value $ligne

    $couleur = switch ($Niveau) {
        "OK"    { "Green" }
        "WARN"  { "Yellow" }
        "ERROR" { "Red" }
        default { "White" }
    }
    Write-Host $ligne -ForegroundColor $couleur
}

# Creer le dossier de logs
$dossierLog = Split-Path $FichierLog -Parent
if (-not (Test-Path $dossierLog)) {
    New-Item -Path $dossierLog -ItemType Directory -Force | Out-Null
}

Write-Log "=== Debut de la maintenance ==="

# Etape 1 : Nettoyage des fichiers temporaires
Write-Log "Etape 1 : Nettoyage des fichiers temporaires"
try {
    $fichiersTemp = Get-ChildItem -Path $env:TEMP -Recurse -File -ErrorAction SilentlyContinue
    $totalAvant = ($fichiersTemp | Measure-Object Length -Sum).Sum
    $compteur = 0

    foreach ($f in $fichiersTemp) {
        try {
            Remove-Item $f.FullName -Force -ErrorAction Stop
            $compteur++
        } catch {
            # Fichier verrouille, on ignore
        }
    }

    $totalApres = (Get-ChildItem -Path $env:TEMP -Recurse -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
    $economieMB = [math]::Round(($totalAvant - $totalApres) / 1MB, 2)
    Write-Log "$compteur fichiers supprimes ($economieMB Mo liberes)" "OK"
} catch {
    Write-Log "Erreur nettoyage temporaires : $($_.Exception.Message)" "ERROR"
}

# Etape 2 : Verification de l'espace disque
Write-Log "Etape 2 : Verification espace disque"
try {
    $disques = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3"
    foreach ($d in $disques) {
        $pourcent = [math]::Round(100 - ($d.FreeSpace / $d.Size * 100), 1)
        $libreGB = [math]::Round($d.FreeSpace / 1GB, 2)

        if ($pourcent -gt 90) {
            Write-Log "$($d.DeviceID) a ${pourcent}% (${libreGB} Go libre) - CRITIQUE" "ERROR"
        } elseif ($pourcent -gt 80) {
            Write-Log "$($d.DeviceID) a ${pourcent}% (${libreGB} Go libre) - Attention" "WARN"
        } else {
            Write-Log "$($d.DeviceID) a ${pourcent}% (${libreGB} Go libre)" "OK"
        }
    }
} catch {
    Write-Log "Erreur verification disque : $($_.Exception.Message)" "ERROR"
}

# Etape 3 : Verification des services critiques
Write-Log "Etape 3 : Verification des services"
$servicesCritiques = @("W32Time", "EventLog", "Winmgmt", "LanmanServer")
foreach ($svc in $servicesCritiques) {
    try {
        $etat = Get-Service -Name $svc -ErrorAction Stop
        if ($etat.Status -ne "Running") {
            Write-Log "$svc arrete, tentative de redemarrage..." "WARN"
            Start-Service -Name $svc -ErrorAction Stop
            Write-Log "$svc redemarrage reussi" "OK"
        } else {
            Write-Log "$svc : en cours d'execution" "OK"
        }
    } catch {
        Write-Log "Erreur service $svc : $($_.Exception.Message)" "ERROR"
    }
}

# Etape 4 : Rotation des logs anciens
Write-Log "Etape 4 : Rotation des logs"
try {
    $seuilJours = 30
    $logsAnciens = Get-ChildItem "C:\Logs" -Filter "*.log" -File -ErrorAction SilentlyContinue |
        Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$seuilJours) }

    if ($logsAnciens.Count -gt 0) {
        $tailleTotale = [math]::Round(($logsAnciens | Measure-Object Length -Sum).Sum / 1MB, 2)
        $logsAnciens | Remove-Item -Force
        Write-Log "$($logsAnciens.Count) logs anciens supprimes ($tailleTotale Mo)" "OK"
    } else {
        Write-Log "Aucun log ancien a supprimer" "INFO"
    }
} catch {
    Write-Log "Erreur rotation logs : $($_.Exception.Message)" "ERROR"
}

Write-Log "=== Maintenance terminee ==="

Aide-memoire rapide

Equivalences Bash / PowerShell

TacheBashPowerShell
Lister fichiersls -laGet-ChildItem
Changer repertoirecd /cheminSet-Location C:\chemin
Afficher contenucat fichierGet-Content fichier
Copiercp src destCopy-Item src dest
Deplacermv src destMove-Item src dest
Supprimerrm fichierRemove-Item fichier
Creer repertoiremkdir dossierNew-Item -ItemType Directory dossier
Processusps auxGet-Process
Servicessystemctl status svcGet-Service svc
Reseauip addrGet-NetIPAddress
Pingping -c 4 hostTest-NetConnection host
DNSnslookup hostResolve-DnsName host
Utilisateurscat /etc/passwdGet-ADUser -Filter *
Disquedf -hGet-PSDrive ou Get-CimInstance Win32_LogicalDisk
Recherche textegrep motif fichierSelect-String -Pattern motif fichier
Triersort fichierGet-Content fichier | Sort-Object
Compter ligneswc -l fichier(Get-Content fichier).Count
Variable envexport VAR=val$env:VAR = "val"
Taches planifieescrontab -eRegister-ScheduledTask
Execution distantessh user@host cmdInvoke-Command -ComputerName host

Codes de retour et gestion d'erreur

ConceptBashPowerShell
Code retour$? (0=succes)$LASTEXITCODE
Derniere erreur$?$Error[0]
Arreter sur erreurset -e$ErrorActionPreference = "Stop"
Try/catchPas natif (if/then)try { } catch { } finally { }
Rediriger erreurs2>/dev/null-ErrorAction SilentlyContinue