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
PATHré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 :
- 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. - Clic droit sur ce dossier > Créer une tâche… (et non « Créer une tâche de base », qui masque les options utiles).
- 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é. - Onglet Déclencheurs > Nouveau… : Selon une planification, Mensuel, le jour 1, à 02:00:00.
- 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. - 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é :
$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 :
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.
| Ligne | Quand |
|---|---|
0 2 * * * | Tous les jours à 2 h 00 |
*/15 * * * * | Toutes les 15 minutes |
*/15 8-18 * * 1-5 | Toutes 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 * * 0 | Le dimanche à 22 h 00 |
0 8,12,17 * * * | À 8 h, 12 h et 17 h |
@reboot | Au 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 aveccrontab -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.
# /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.
# /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
# /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
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.
| cron | Timer systemd | Planificateur de tâches | |
|---|---|---|---|
| Système | Linux | Linux avec systemd | Windows |
| Où se règle la tâche | Une ligne de crontab | Deux fichiers d'unités | Console ou Register-ScheduledTask |
| Sortie du script | À rediriger soi-même | Dans le journal, automatiquement | À journaliser dans le script |
| Exécution manquée (machine éteinte) | Perdue | Rattrapée avec Persistent=true | Rattrapée avec -StartWhenAvailable |
| Deux exécutions en même temps | Possible, sauf flock | Impossible : le service tourne déjà | Réglable (-MultipleInstances) |
| Dernier résultat | À chercher dans les journaux | systemctl status | Get-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.servicedans 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.
0 3 * * 6*/5 * * * *0 9-17 * * 1-545 23 28-31 * *0 0 1 1,4,7,10 *
Voir la réponseMasquer la réponse de l'exercice 1
- Le samedi à 3 h 00 (6 = samedi).
- Toutes les 5 minutes, jour et nuit : à 0, 5, 10… 55 minutes de chaque heure.
- À l'heure pile, de 9 h à 17 h, du lundi au vendredi : 9 exécutions par jour ouvré.
- À 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 ] && …. - À 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
sauvegarde-config.shsans chemin : lePATHde 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 unPATHdéfini en tête de la crontab.- 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 écriredate +\%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
$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
# /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
# /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
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é ?