Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1417 minDébutant

Planifier un script : cron, timers systemd et Planificateur de tâches

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • lire et écrire une ligne de crontab ;
  • créer un timer systemd et vérifier ses exécutions ;
  • planifier une tâche Windows, dans la console et en PowerShell ;
  • choisir le bon compte et être prévenu en cas d'échec.

4 exercices corrigés1 schéma

Planifier un script, c'est confier à un service du système le soin de le lancer seul, à heure fixe ou à intervalle régulier, sans session ouverte. Sous Windows, c'est le rôle du Planificateur de tâches ; sous Linux, de cron et des timers systemd. Ce chapitre montre comment planifier avec chacun, avec quel compte, et comment savoir le lendemain matin si la tâche de la nuit a réussi.

Ce qui change quand personne ne regarde

Un script qui marche quand tu le lances à la main peut échouer en tâche planifiée, pour des raisons qui reviennent toujours :

  • un autre compte : la tâche tourne sous un compte de service ou sous SYSTEM, avec d'autres droits, un autre dossier personnel, sans tes lecteurs réseau ;
  • un autre environnement : pas de profil PowerShell, un PATH réduit sous cron, un autre dossier courant ;
  • personne pour répondre : un Read-Host, une demande de mot de passe ou une confirmation bloquent la tâche indéfiniment ;
  • personne pour lire l'écran : un message d'erreur affiché n'est vu par personne, seuls comptent le journal et le code de sortie.

D'où les règles déjà vues : chemins absolus (ou $PSScriptRoot), -NoProfile -NonInteractive, aucune question, un journal, un code de sortie.

Le Planificateur de tâches de Windows

Dans la console Planificateur de tâches (taskschd.msc), Chloé crée la tâche qui lance le 1er de chaque mois, à 2 h, le rapport d'audit des comptes du chapitre 12 :

  1. Dans l'arborescence de gauche, sélectionner Bibliothèque du Planificateur de tâches, puis créer un dossier Sorbier (clic droit > Nouveau dossier) pour ranger les tâches de l'entreprise à part de celles du système.
  2. Clic droit sur ce dossier > Créer une tâche… (et non « Créer une tâche de base », qui masque les options utiles).
  3. Onglet Général : un nom explicite (Audit mensuel des comptes), une description, le compte d'exécution (Modifier l'utilisateur ou le groupe…), puis Exécuter même si l'utilisateur n'est pas connecté.
  4. Onglet Déclencheurs > Nouveau… : Selon une planification, Mensuel, le jour 1, à 02:00:00.
  5. Onglet Actions > Nouveau… : Démarrer un programme. Programme : "C:\Program Files\PowerShell\7\pwsh.exe". Arguments : -NoProfile -NonInteractive -File "C:\Scripts\Get-AuditComptes.ps1". Commencer dans : C:\Scripts.
  6. Onglet Paramètres : cocher Arrêter la tâche si elle s'exécute plus de 1 heure, et choisir Ne pas démarrer une nouvelle instance si la tâche est déjà en cours.

Dans le volet Actions de droite, Activer l'historique de toutes les tâches garde la trace de chaque exécution. La colonne Résultat de la dernière exécution affiche le code de sortie du script : (0x0) pour un succès, (0x1) si le script a fait exit 1.

En PowerShell, New-ScheduledTaskTrigger ne connaît pas le rythme mensuel : pour cette tâche-là, on passe par la console ou par l'ancienne commande schtasks /create /sc monthly. Pour une tâche quotidienne ou hebdomadaire, PowerShell permet de l'écrire une fois et de la redéployer à l'identique sur d'autres serveurs. Voici l'export quotidien de l'annuaire, à 2 h, sous un compte de service géré :

powershell
$action = New-ScheduledTaskAction -Execute 'C:\Program Files\PowerShell\7\pwsh.exe' `
    -Argument '-NoProfile -NonInteractive -File "C:\Scripts\Export-Annuaire.ps1" -Dossier "D:\Exports"' `
    -WorkingDirectory 'C:\Scripts'
$declencheur = New-ScheduledTaskTrigger -Daily -At '02:00'
$compte = New-ScheduledTaskPrincipal -UserId 'SORBIER\svc-rapports$' -LogonType Password
$reglages = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 1) `
    -MultipleInstances IgnoreNew -StartWhenAvailable

Register-ScheduledTask -TaskName 'Export de l''annuaire' -TaskPath '\Sorbier\' `
    -Action $action -Trigger $declencheur -Principal $compte -Settings $reglages `
    -Description 'Export CSV quotidien des comptes actifs, pour l''accueil'

New-ScheduledTaskTrigger propose -Once, -Daily, -Weekly (avec -DaysOfWeek Monday), -AtStartup et -AtLogOn. -StartWhenAvailable rattrape une exécution manquée parce que le serveur était éteint. Pour suivre la tâche :

powershell
Get-ScheduledTaskInfo -TaskName "Export de l'annuaire" -TaskPath '\Sorbier\' |
    Select-Object LastRunTime, LastTaskResult, NextRunTime
Start-ScheduledTask -TaskName "Export de l'annuaire" -TaskPath '\Sorbier\'      # lancer tout de suite, pour tester

LastTaskResult vaut 0 après un succès. La valeur 267009 (0x41301 en hexadécimal) signifie que la tâche est en cours d'exécution, 267011 (0x41303) qu'elle n'a encore jamais tourné.

cron sous Linux

cron lit des tables de planification, les crontabs, et lance chaque commande à l'heure indiquée. Chaque ligne commence par cinq champs de date et d'heure, puis la commande.

30minute0 à 592heure0 à 23*jour du mois1 à 31*mois1 à 121-5jour semaine0 à 7, 0 = dim./usr/local/sbin/sauvegarde-config.shla commande, avec son chemin absoluLecture : à 2 h 30, du lundi au vendredi*toutes lesvaleurs8,12,17une liste8 h, 12 h et 17 h1-5un intervallebornes comprises*/15un pastoutes les 15
Les cinq champs d'une ligne de crontab : minute, heure, jour du mois, mois, jour de la semaine
LigneQuand
0 2 * * *Tous les jours à 2 h 00
*/15 * * * *Toutes les 15 minutes
*/15 8-18 * * 1-5Toutes les 15 minutes de 8 h 00 à 18 h 45, du lundi au vendredi
30 6 1 * *Le 1er de chaque mois à 6 h 30
0 22 * * 0Le dimanche à 22 h 00
0 8,12,17 * * *À 8 h, 12 h et 17 h
@rebootAu démarrage du système

Dans l'exemple */15 8-18, le dernier lancement a lieu à 18 h 45, puisque l'heure 18 est incluse et que les minutes 0, 15, 30 et 45 de chaque heure sont prises. Un piège connu : quand le jour du mois et le jour de la semaine sont tous les deux précisés (0 9 1 * 1), cron lance la commande si l'un ou l'autre correspond (le 1er du mois, et aussi chaque lundi), pas les deux à la fois.

Deux endroits pour écrire ces lignes :

  • La crontab d'un utilisateur, éditée avec crontab -e (et listée avec crontab -l). Les cinq champs, puis la commande, lancée avec les droits de cet utilisateur.
  • Un fichier dans /etc/cron.d/, appartenant à root, avec un sixième champ : le compte qui exécute la commande. C'est la méthode à préférer pour un serveur : le fichier se déploie, se versionne et se lit comme n'importe quelle configuration.
text
# /etc/cron.d/sorbier : tâches planifiées de srv-web
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""

# min heure jour mois jsem  compte  commande
15  2   *    *    *     root    /usr/local/sbin/sauvegarde-config.sh >> /var/log/sauvegarde-config.log 2>&1
*/10 *  *    *    *     root    flock -n /run/lock/verif-disques.lock /usr/local/bin/verif-disques.sh > /dev/null
0   7   *    *    1-5   root    /usr/local/bin/rapport-ssh.sh 10 >> /var/log/rapport-ssh.log 2>&1

Sur Debian, les scripts déposés dans /etc/cron.daily/, /etc/cron.weekly/ ou /etc/cron.monthly/ sont lancés une fois par jour, par semaine ou par mois, sans écrire de ligne : ils doivent être exécutables et leur nom ne doit pas contenir de point, sinon run-parts les ignore (sauvegarde-config, pas sauvegarde-config.sh). Les exécutions de cron apparaissent dans le journal : journalctl -u cron --since today.

Les timers systemd

Un timer systemd est l'autre façon de planifier sous Linux, celle qu'utilise Debian pour ses propres tâches (logrotate.timer, apt-daily.timer). Il faut deux fichiers : un service, qui dit quoi lancer, et un timer, qui dit quand.

ini
# /etc/systemd/system/sauvegarde-config.service
[Unit]
Description=Sauvegarde de la configuration de srv-web
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/sauvegarde-config.sh
ini
# /etc/systemd/system/sauvegarde-config.timer
[Unit]
Description=Sauvegarde de la configuration chaque nuit

[Timer]
OnCalendar=*-*-* 02:15:00
Persistent=true
RandomizedDelaySec=10min

[Install]
WantedBy=timers.target
bash
sudo systemctl daemon-reload                         # relire les fichiers d'unités
sudo systemctl enable --now sauvegarde-config.timer  # activer au démarrage et démarrer le timer
systemctl list-timers sauvegarde-config.timer        # dernière et prochaine exécution
sudo systemctl start sauvegarde-config.service       # lancer tout de suite, pour tester
journalctl -u sauvegarde-config.service -n 30        # la sortie complète des dernières exécutions
systemctl status sauvegarde-config.service           # le résultat de la dernière exécution, code de sortie compris

OnCalendar suit la forme jour-de-semaine année-mois-jour heure:minute:seconde. Quelques valeurs : daily (chaque jour à 0 h), *-*-* 02:15:00 (chaque jour à 2 h 15), Mon *-*-* 08:00:00 (chaque lundi à 8 h), *:0/15 (toutes les 15 minutes). systemd-analyze calendar '*-*-* 02:15:00' affiche la prochaine échéance d'une expression, pour vérifier qu'elle dit bien ce que tu veux.

Persistent=true lance la tâche au démarrage si l'échéance a été manquée pendant que le serveur était éteint. RandomizedDelaySec étale les démarrages : utile quand vingt serveurs ont la même sauvegarde à 2 h 15 et la même destination.

cronTimer systemdPlanificateur de tâches
SystèmeLinuxLinux avec systemdWindows
Où se règle la tâcheUne ligne de crontabDeux fichiers d'unitésConsole ou Register-ScheduledTask
Sortie du scriptÀ rediriger soi-mêmeDans le journal, automatiquementÀ journaliser dans le script
Exécution manquée (machine éteinte)PerdueRattrapée avec Persistent=trueRattrapée avec -StartWhenAvailable
Deux exécutions en même tempsPossible, sauf flockImpossible : le service tourne déjàRéglable (-MultipleInstances)
Dernier résultatÀ chercher dans les journauxsystemctl statusGet-ScheduledTaskInfo

Pour un nouveau script sur un serveur Debian, le timer systemd est le meilleur choix ; cron reste partout, plus simple pour une ligne rapide, et c'est lui que tu croiseras sur les serveurs existants.

Être prévenu en cas d'échec

Une tâche planifiée qui échoue en silence est pire qu'une tâche qui n'existe pas : tout le monde croit la sauvegarde faite. Il faut qu'un échec se voie :

  • le script renvoie un code de sortie différent de 0 et écrit dans le journal du système (logger, journal des événements Windows) ;
  • la supervision surveille ce résultat : Zabbix peut lire l'état d'un service systemd, un fichier de journal ou le résultat d'une tâche planifiée Windows, et alerter (cours Supervision) ;
  • avec systemd, la directive OnFailure=alerte@%n.service dans la section [Unit] du service lance une autre unité, qui envoie une alerte, quand le service échoue.

Exercices

Exercice 1 sur 4 Lire des lignes de crontab Réussi À revoir

Dis en français quand s'exécute chaque ligne.

  1. 0 3 * * 6
  2. */5 * * * *
  3. 0 9-17 * * 1-5
  4. 45 23 28-31 * *
  5. 0 0 1 1,4,7,10 *
Voir la réponseMasquer la réponse de l'exercice 1
  1. Le samedi à 3 h 00 (6 = samedi).
  2. Toutes les 5 minutes, jour et nuit : à 0, 5, 10… 55 minutes de chaque heure.
  3. À l'heure pile, de 9 h à 17 h, du lundi au vendredi : 9 exécutions par jour ouvré.
  4. À 23 h 45, les 28, 29, 30 et 31 de chaque mois. Ce n'est pas « le dernier jour du mois » : en avril, la commande tourne les 28, 29 et 30. Pour le dernier jour seulement, on lance chaque jour de 28 à 31 et le script vérifie que le lendemain est le 1er : [ "$(date -d tomorrow +\%d)" = 01 ] && ….
  5. À minuit, le 1er janvier, le 1er avril, le 1er juillet et le 1er octobre : chaque début de trimestre.

Tu avais trouvé ?

Exercice 2 sur 4 La tâche qui ne marche qu'à la main Réussi À revoir

Sur srv-web, sudo /usr/local/sbin/sauvegarde-config.sh fonctionne. Moussa ajoute à la crontab de root (sudo crontab -e) la ligne 0 2 * * * sauvegarde-config.sh > /var/log/sauv-$(date +%F).log, mais le lendemain, aucun fichier de journal n'a été créé. Trouve les deux erreurs et propose une meilleure solution.

Voir la réponseMasquer la réponse de l'exercice 2
  1. sauvegarde-config.sh sans chemin : le PATH de cron est /usr/bin:/bin, qui ne contient pas /usr/local/sbin. La commande est introuvable. Il faut le chemin absolu /usr/local/sbin/sauvegarde-config.sh, ou un PATH défini en tête de la crontab.
  2. Le % non protégé : cron coupe la commande au premier % et envoie le reste sur l'entrée standard. La commande réellement lancée se termine par /var/log/sauv-$(date +, une erreur de syntaxe. Il faut écrire date +\%F.

La meilleure solution est un timer systemd : la sortie du script part automatiquement dans le journal (journalctl -u sauvegarde-config.service), les dates sont dans le script, et systemctl status montre le résultat de la dernière exécution. À défaut, un fichier /etc/cron.d/sorbier avec PATH, chemin absolu et redirection vers un journal fixe (>> /var/log/sauvegarde-config.log 2>&1).

Tu avais trouvé ?

Exercice 3 sur 4 Planifier en PowerShell Réussi À revoir

Écris les commandes qui créent sur srv-fichiers, dans le dossier \Sorbier\, une tâche Nettoyage des exports qui lance chaque dimanche à 23 h le script C:\Scripts\Remove-VieuxExports.ps1 avec Windows PowerShell 5.1, sous le compte SYSTEM, sans jamais durer plus de 30 minutes. Puis la commande qui affiche le résultat de sa dernière exécution.

Voir la réponseMasquer la réponse de l'exercice 3
powershell
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
    -Argument '-NoProfile -NonInteractive -File "C:\Scripts\Remove-VieuxExports.ps1"'
$declencheur = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At '23:00'
$compte = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$reglages = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 30)
Register-ScheduledTask -TaskName 'Nettoyage des exports' -TaskPath '\Sorbier\' `
    -Action $action -Trigger $declencheur -Principal $compte -Settings $reglages

Get-ScheduledTaskInfo -TaskName 'Nettoyage des exports' -TaskPath '\Sorbier\' |
    Select-Object LastRunTime, LastTaskResult

SYSTEM convient ici car la tâche ne touche que des fichiers locaux. Si elle devait écrire sur un partage d'un autre serveur, il faudrait un gMSA : sur le réseau, SYSTEM s'authentifie avec le compte d'ordinateur de srv-fichiers, qui n'a pas à recevoir de droits sur les partages.

Tu avais trouvé ?

Exercice 4 sur 4 Écrire un timer Réussi À revoir

Écris les deux fichiers d'unités qui lancent /usr/local/bin/rapport-ssh.sh 10 chaque jour ouvré à 7 h, sous le compte rapports (qui doit faire partie du groupe systemd-journal pour lire les journaux), avec rattrapage si le serveur était éteint. Donne les commandes pour activer le timer et vérifier sa prochaine échéance.

Voir la réponseMasquer la réponse de l'exercice 4
ini
# /etc/systemd/system/rapport-ssh.service
[Unit]
Description=Rapport quotidien des échecs SSH

[Service]
Type=oneshot
User=rapports
ExecStart=/usr/local/bin/rapport-ssh.sh 10
ini
# /etc/systemd/system/rapport-ssh.timer
[Unit]
Description=Rapport des échecs SSH, chaque jour ouvré à 7 h

[Timer]
OnCalendar=Mon..Fri *-*-* 07:00:00
Persistent=true

[Install]
WantedBy=timers.target
bash
sudo usermod -aG systemd-journal rapports
sudo systemctl daemon-reload
sudo systemctl enable --now rapport-ssh.timer
systemctl list-timers rapport-ssh.timer

Mon..Fri désigne les jours du lundi au vendredi. Le rapport s'affiche dans le journal : journalctl -u rapport-ssh.service --since today.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Sécurité des scripts : stratégie d'exécution, secrets et signatureRevoir le sommaire du cours