Les tâches du Planificateur de tâches qui ne s'exécutent pas ou se terminent en 0x1 — isoler la cause et concevoir une exploitation fiable

· Mis à jour le: · · Planificateur de tâches, Windows, PowerShell, Automatisation métier, Traitement par lots, Exploitation, Dépannage, Conseil technique

« Je veux faire tourner mon script d’agrégation PowerShell tous les matins à 6 h. » « Ça marche quand je l’exécute à la main, mais dès que je le mets dans le Planificateur de tâches, ça ne fonctionne plus. » Quand on nous consulte sur l’automatisation métier, la discussion finit presque toujours par atterrir ici.

Sur ce blog, nous avons déjà écrit une série d’articles sur l’automatisation : l’automatisation de la maintenance des journaux, les tests de scripts avec Pester, l’exécution de PowerShell depuis C#, et l’automatisation métier avec Power Automate. Tous ces articles supposent en définitive que la tâche en question finira par tourner de manière planifiée via le Planificateur de tâches. Or ce dernier s’avère être un mécanisme étonnamment capricieux, et les incidents du type « ça marche à la main mais échoue en planifié » ou « personne ne s’est rendu compte que ça s’était arrêté sans bruit » ne cessent jamais vraiment de se produire.

Cet article passe en revue les aspects du Planificateur de tâches qui débouchent directement sur des incidents d’exploitation — comptes d’exécution et types de connexion, isolation de la cause quand une tâche « ne s’exécute pas », causes typiques du code de retour 0x1, comment conserver des journaux, et le contrôle des exécutions multiples — dans l’ordre où l’on y trébuche généralement en pratique.

1. L’essentiel d’abord

  • La plupart des problèmes du Planificateur de tâches ne viennent pas du script lui-même, mais d’un décalage dans la compréhension de « en tant que qui, et dans quel type de session, la tâche s’exécute ». Dès l’instant où vous choisissez « Exécuter que l’utilisateur soit connecté ou non », concevez votre solution en partant du principe qu’elle tourne dans un monde distinct à la fois de la session interactive et de l’environnement de connexion.1
  • L’enquête sur « ça ne s’exécute pas » part de l’onglet Historique et du journal des événements. L’historique des tâches étant désactivé par défaut, veillez à activer « Activer l’historique de toutes les tâches » avant la mise en exploitation.2
  • Le 0x1 affiché dans « Dernier résultat d’exécution » n’est pas une erreur du Planificateur de tâches : cela signifie que le programme lancé lui-même a renvoyé le code de sortie 1. La cause se trouve donc côté script : concevez d’abord vos codes de sortie et mettez en place votre propre mécanisme de journalisation.3
  • Pour un script PowerShell, la forme de base consiste à l’appeler avec -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "chemin complet", en basant les chemins internes au script sur $PSScriptRoot. C’est aussi là qu’on tombe classiquement dans le piège consistant à mettre des guillemets dans le champ « Commencer dans (facultatif) », ce qu’il ne faut surtout pas faire.
  • Pour éviter l’incident où une tâche meurt silencieusement après un changement de mot de passe, il faut concevoir correctement le compte d’exécution (inventaire des comptes de service, et envisager un gMSA en environnement de domaine).4
  • Le contrôle des exécutions multiples (par défaut « Ne pas démarrer une nouvelle instance »), la limite de temps d’exécution (3 jours par défaut) et les conditions d’alimentation (par défaut, secteur uniquement) sont des réglages classiques que l’on laisse à leur valeur par défaut sans s’en rendre compte. Décidez-les toujours explicitement au moment de l’enregistrement.5

2. Anatomie du Planificateur de tâches — déclencheurs, actions, conditions, paramètres

Une tâche du Planificateur de tâches est constituée de quatre grands éléments.

Élément Contenu Point propice aux incidents
Déclencheur Quand la tâche démarre (heure, connexion, événement, etc.) La gestion d’un déclencheur horaire manqué (StartWhenAvailable, voir plus loin)
Action Ce qui s’exécute (programme, arguments, dossier de démarrage) Erreurs de guillemets dans les arguments, mauvaise indication du dossier de démarrage
Conditions Si l’exécution est autorisée (alimentation, réseau, inactivité) « Secteur uniquement » est activé par défaut
Paramètres Comportement pendant l’exécution (exécutions multiples, limite de temps, nouvelles tentatives) Démarrer l’exploitation sans avoir vérifié les valeurs par défaut

Une tâche créée via l’interface graphique (taskschd.msc) peut être exportée en XML. Si vous voulez gérer les définitions de tâches sous Git, ou déployer la même tâche sur plusieurs machines, il est recommandé de les scripter — soit par export XML puis schtasks /Create /XML, soit avec le module PowerShell ScheduledTasks (New-ScheduledTaskAction / New-ScheduledTaskTrigger / New-ScheduledTaskSettingsSet / Register-ScheduledTask).5

$action   = New-ScheduledTaskAction -Execute 'pwsh.exe' `
    -Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"' `
    -WorkingDirectory 'C:\Jobs'
$trigger  = New-ScheduledTaskTrigger -Daily -At '06:00'
# La condition d'alimentation par défaut est « démarrer uniquement sur secteur, arrêter si on bascule sur batterie ».
# Pour une tâche devant aussi tourner sur un portable ou un poste de terrain, autorisez-le explicitement ici
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable `
    -MultipleInstances IgnoreNew `
    -ExecutionTimeLimit (New-TimeSpan -Hours 2) `
    -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
# Récupérer les identifiants via Get-Credential pour que le mot de passe n'apparaisse jamais à l'écran
$cred = Get-Credential -UserName 'DOMAIN\svc-batch' -Message 'Identifiants du compte d''exécution'
Register-ScheduledTask -TaskName 'KS-Cleanup-Logs' `
    -Action $action -Trigger $trigger -Settings $settings `
    -User $cred.UserName -Password $cred.GetNetworkCredential().Password

Une fois la définition de la tâche transformée en code, ce qui a été validé sur une machine de test peut être transposé tel quel en production, et vous évitez de vous retrouver dans une situation où « personne ne sait plus avec quels paramètres ça tourne ».

Pour un déploiement sur plusieurs machines, exporter en XML une tâche construite via l’interface graphique puis la distribuer avec schtasks est également une approche éprouvée.

rem Exporter la tâche créée sur la machine de test
schtasks /Query /TN "KS-Cleanup-Logs" /XML > KS-Cleanup-Logs.xml

rem Importer sur chaque poste (indiquer le compte d'exécution et le mot de passe lors de l'enregistrement)
schtasks /Create /TN "KS-Cleanup-Logs" /XML KS-Cleanup-Logs.xml /RU DOMAIN\svc-batch /RP *

Le XML contenant l’intégralité des déclencheurs, conditions et paramètres, le conserver dans un dépôt permet à la fois de relire les définitions de tâches et de gérer leurs différences. À l’inverse, enregistrer manuellement la même tâche via l’interface graphique sur dix machines finit immanquablement par produire une « tâche égarée » dont les réglages diffèrent sur une seule machine. Il est plus sûr de la coder avant que le nombre de postes n’atteigne deux chiffres.

Notez que le contenu de cet article suppose le Planificateur de tâches (la lignée Task Scheduler 2.0) de Windows 10/11 et Windows Server 2016 ou ultérieur. Si votre environnement conserve encore des tâches héritées de l’ancienne commande at, commencez par en faire l’inventaire.

3. Comptes d’exécution et types de connexion — la première source d’incidents

Les choix « Exécuter uniquement si l’utilisateur est connecté » et « Exécuter que l’utilisateur soit connecté ou non », dans les propriétés de la tâche, correspondent en interne à un choix de type de connexion (LogonType). Si votre compréhension de ce point est décalée, vous finirez par tourner en rond sans pouvoir expliquer la plupart des cas de « ça marche à la main mais pas en planifié ».1

3.1 Différences entre les trois modes

Choix Mécanisme interne Caractéristiques / contraintes
Exécuter uniquement si l’utilisateur est connecté Jeton interactif (InteractiveToken) La fenêtre est visible à l’écran pendant la connexion. Ne démarre pas du tout si l’utilisateur est déconnecté
Exécuter que l’utilisateur soit connecté ou non Mot de passe stocké (Password) Le mot de passe est enregistré à l’inscription. Aucune fenêtre visible (non interactif). Un changement de mot de passe fait échouer le démarrage
Idem + « Ne pas stocker le mot de passe » S4U Aucun mot de passe stocké, mais en contrepartie impossible d’accéder aux ressources réseau et aux fichiers chiffrés (EFS)1

Voici les incidents typiques rencontrés en pratique.

  • Un script accédant à un dossier partagé a été enregistré avec « Ne pas stocker le mot de passe » (S4U). Il fonctionnait dans les tests locaux, mais en production, seul l’accès au dossier partagé échouait. → Parce que S4U n’a pas d’identifiants réseau.
  • Une tâche enregistrée avec « Exécuter que l’utilisateur soit connecté ou non » a tourné plusieurs mois, jusqu’à ce que l’expiration du mot de passe de domaine entraîne son changement. À partir de là, la tâche n’a cessé d’échouer à la connexion (0x8007052E), sans que personne ne s’en aperçoive.
  • Une tâche lançant une application graphique a été enregistrée en mode « quel que soit l’état de connexion ». L’application démarrait bien, mais sa fenêtre n’apparaissait nulle part, ce qui a été interprété à tort comme « ça ne fonctionne pas ». → Parce qu’elle s’exécute dans une session non interactive : tout traitement nécessitant un affichage interactif ne peut fondamentalement pas fonctionner dans cette configuration.

Par ailleurs, le compte exécutant la tâche en mode Password ou S4U a besoin du droit « Ouvrir une session en tant que tâche par lots » (SeBatchLogonRight). Les Administrateurs l’ont par défaut, mais si vous transformez un utilisateur standard dédié en compte de service, vérifiez également les paramètres de la stratégie de sécurité locale.6

3.2 Quel compte utiliser

  • SYSTEM : puissant et ne nécessitant aucune gestion de mot de passe, mais ses privilèges sont trop étendus. Pratique pour des tâches de maintenance purement locales, il faut néanmoins éviter d’exécuter en SYSTEM n’importe quelle tâche touchant à des données métier. Pour la façon de déterminer si les droits administrateur sont réellement nécessaires, voir notre autre article « Quand une application Windows a-t-elle vraiment besoin des droits administrateur ».
  • Un compte de service dédié (utilisateur standard) : permet d’appliquer le principe du moindre privilège, mais impose de mettre à jour la tâche à chaque changement de mot de passe. Gérez ensemble l’expiration du mot de passe et l’inventaire des tâches.
  • gMSA (compte de service géré par groupe) : le premier choix en environnement de domaine. Le mot de passe étant géré automatiquement par le contrôleur de domaine, le problème même de « la tâche meurt lors d’un changement de mot de passe » disparaît. Le Planificateur de tâches prend en charge l’exécution sous un gMSA.4

Notez aussi que la case « Exécuter avec les autorisations maximales » signifie exécuter avec le jeton administrateur (élevé) parmi les jetons scindés par l’UAC. Ne la cochez pas pour les tâches qui n’ont pas besoin de droits administrateur.

4. Étapes pour isoler la cause quand une tâche « ne s’exécute pas »

4.1 Ne soupçonner qu’après avoir activé l’historique

« Activer l’historique de toutes les tâches », dans le volet de droite du Planificateur de tâches, est désactivé par défaut. Tant que l’historique reste désactivé, même le fait qu’une exécution ait échoué n’est enregistré nulle part. Activez-le impérativement avant la mise en exploitation, de façon à pouvoir le vérifier aux côtés du journal des événements (Observateur d’événements → Journaux des applications et des services → Microsoft → Windows → TaskScheduler → Operational).2

La démarche de base pour isoler la cause fonctionne parfaitement en suivant telle quelle la logique du guide de dépannage de Microsoft.2

  1. Tester le script séparément — avant de l’intégrer dans une tâche, vérifiez que le script se termine correctement, dans des conditions équivalentes à celles du compte d’exécution (avec runas ou une machine de test si possible).
  2. Regarder la colonne État et l’onglet Historique — distinguer si la tâche a même été déclenchée, ou si elle a été déclenchée mais a échoué. Si elle n’a pas été déclenchée, soupçonnez les réglages du déclencheur ou les conditions (alimentation, réseau), et vérifiez si l’action elle-même fonctionne via une exécution manuelle (clic droit → Exécuter).
  3. Basculer temporairement sur « Exécuter uniquement si l’utilisateur est connecté » — si cela fonctionne, vous pouvez restreindre la cause à la session non interactive ou aux identifiants (chapitre précédent).

4.2 Comment lire « Dernier résultat d’exécution »

Valeur affichée Signification
0x0 Terminé normalement (le programme lancé a renvoyé le code de sortie 0)
0x1 Le programme lancé a renvoyé le code de sortie 1 (ce n’est pas une erreur du Planificateur de tâches lui-même)
0x41300 En attente de la prochaine exécution planifiée (SCHED_S_TASK_READY)
0x41301 En cours d’exécution (SCHED_S_TASK_RUNNING)
0x41303 Ne s’est encore jamais exécutée (SCHED_S_TASK_HAS_NOT_RUN)
0x8007010B Le dossier de démarrage (« Commencer dans (facultatif) ») est mal spécifié. Symptôme classique lorsqu’on y a mis des guillemets
0x8007052E Échec de connexion. Mot de passe stocké obsolète, droit manquant, etc.

Les codes de la famille 0x413xx sont des codes d’état du Planificateur de tâches, ceux de la forme 0x8007xxxx sont des codes d’erreur Windows, et les petites valeurs comme 0x1 ou 0x2 sont des codes de sortie du programme lancé lui-même.3 Une fois cette distinction acquise, vous ne risquez plus de chercher au mauvais endroit dès le départ (réglages de la tâche ou script).

4.3 Attention aux valeurs par défaut des conditions et paramètres

  • Conditions d’alimentation : par défaut, « Ne démarrer la tâche que si l’ordinateur est alimenté sur secteur » est activé. Utiliser un ordinateur portable comme machine de test produit alors un « bug non reproductible » qui n’apparaît que sur batterie. De plus, à partir de Windows 10, tant que le mode d’économie de batterie est actif, le déclenchement de nombreuses tâches est différé.7
  • Heure de démarrage manquée : si le PC était éteint et que l’heure de démarrage prévue est passée, par défaut la tâche ne s’exécute pas avant la prochaine échéance planifiée. Activez explicitement « Si la tâche planifiée n’a pas pu démarrer, l’exécuter dès que possible » (-StartWhenAvailable), ou décidez dès la conception si ce job peut se permettre d’être sauté.5
  • Réveil du mode veille : pour un job nocturne sur un PC autorisé à se mettre en veille, décidez également si vous avez besoin de « Réveiller l’ordinateur pour exécuter cette tâche » (-WakeToRun).

4.4 Attention à la conception du déclencheur elle-même

Il arrive aussi que ce qui ressemble à « la tâche ne s’exécute pas » soit en réalité un déclencheur dont la conception ne correspondait pas à l’intention de départ.

  • « Tous les 31 du mois » ne s’exécute pas les mois qui n’ont pas de 31e jour. Pour un traitement de fin de mois, il est plus sûr d’orienter la conception vers l’intention réelle du « dernier jour du mois » (traiter les données du mois précédent en début de mois suivant, ou laisser le script déterminer lui-même la date).
  • L’heure correspond à l’heure locale de la machine où la tâche est enregistrée. Déployer le même XML sur un poste d’un site à l’étranger, ou parfois sur un serveur configuré en UTC, fait dériver l’heure d’exécution d’un site à l’autre. Décidez dans les spécifications si vous voulez « 6 h heure du Japon partout » ou « 6 h heure locale sur chaque site ».
  • Méfiez-vous si vous commencez à utiliser le Planificateur de tâches pour des répétitions à intervalles courts (toutes les 5 minutes, par exemple). C’est un excellent outil pour un « traitement par lots une fois par jour », mais dès que vous avez besoin d’un scrutin à la minute ou d’une surveillance continue, cela relève du domaine d’un processus résident (voir le chapitre 8 plus loin).
  • Les déclencheurs sur événement sont puissants, mais vérifiez d’abord que l’événement cible est réellement enregistré de manière fiable. Une configuration déclenchée par un ID d’événement spécifique dans le journal des applications peut cesser silencieusement de fonctionner si une mise à jour de l’application change la façon dont l’événement est émis. Un déclencheur horaire combiné à une vérification de condition dans le script s’avère souvent, en définitive, plus facile à suivre.

5. Schémas typiques d’une sortie en 0x1 et bonne façon d’appeler PowerShell

0x1 ne fait que constater l’échec du script : la cause réelle se situe dans une différence de l’environnement d’exécution du script. Ce qui diffère entre une exécution manuelle et une exécution planifiée tient principalement aux points suivants.

  • Le répertoire courant est différent : sans spécifier « Commencer dans (facultatif) », la tâche s’exécute depuis un emplacement comme C:\Windows\System32. Un script écrit avec des chemins relatifs se casse à cet endroit. Côté script, construisez les chemins à partir de $PSScriptRoot ; côté tâche, indiquez le dossier de travail dans « Commencer dans (facultatif) ». Et surtout, ne mettez pas de guillemets dans le champ « Commencer dans (facultatif) » — écrivez-le sans guillemets même pour un chemin contenant des espaces (en ajouter provoque un échec avec 0x8007010B).
  • Les variables d’environnement et le profil diffèrent : considérez que les variables d’environnement définies par un script de connexion ou le profil utilisateur, ainsi que les lecteurs réseau mappés (comme X:), n’existent tout simplement pas dans une session non interactive. Utilisez directement des chemins UNC (\\serveur\partage\...) et éliminez les différences de profil avec -NoProfile.
  • La politique d’exécution diffère : il peut arriver que RemoteSigned soit configuré pour l’utilisateur, mais pas du tout pour le compte de service. Spécifiez explicitement -ExecutionPolicy Bypass dans les arguments de la tâche.
  • Certains outils ont des conventions de code de sortie particulières : robocopy, par exemple, renvoie 1 lorsqu’« il y avait des fichiers à copier et qu’ils ont été copiés avec succès ». Un wrapper qui répercute tel quel le code de sortie peut alors afficher 0x1 alors que tout va bien, ou l’inverse. Vérifiez toujours la convention de code de sortie de chaque commande externe utilisée.

La forme de base pour appeler PowerShell est la suivante.

Programme/script :          pwsh.exe          (powershell.exe pour Windows PowerShell)
Ajout d'arguments :         -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"
Commencer dans (facultatif) : C:\Jobs           (sans guillemets)

Utiliser -File plutôt que -Command simplifie l’échappement des arguments, mais surtout permet au exit n du script de devenir directement le code de sortie du processus, ce qui rend possible de distinguer succès et échec via « Dernier résultat d’exécution » du Planificateur de tâches. Côté script également, concevez-le pour qu’il renvoie explicitement 0 en cas de succès et une valeur non nulle en cas d’échec.

# Réglé sur Stop pour que même une « erreur non terminale » d'une applet de commande tombe dans le catch.
# Sans cela, un échec de Copy-Item, par exemple, peut passer inaperçu et se solder par exit 0
$ErrorActionPreference = 'Stop'

try {
    Main
    exit 0
}
catch {
    Write-Error $_
    exit 1
}

Nous détaillons dans un autre article, « Capturer les exceptions et concevoir la journalisation », où capturer les exceptions et comment les consigner.

6. Conservez vos propres journaux

L’historique du Planificateur de tâches ne vous dit que « si la tâche a démarré et quel était son code de sortie ». Ce que le script a réellement fait, et jusqu’où il est allé, doit être consigné par le script lui-même.

Au minimum, plutôt que de rediriger via les arguments de la tâche (la syntaxe de redirection dans le champ « Arguments » du Planificateur de tâches ne fonctionne pas, faute de passer par un shell), il est plus simple de prendre une transcription directement dans le script.

$logDir = 'C:\Jobs\logs'
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
Start-Transcript -Path (Join-Path $logDir ("cleanup-{0:yyyyMMdd}.log" -f (Get-Date))) -Append
try {
    # traitement principal
}
finally {
    Stop-Transcript
}

Pour traiter le problème de l’accumulation des journaux eux-mêmes (gestion des générations, archivage), le contenu de « PowerShell avancé — automatiser en toute sécurité l’investigation, l’archivage et le reporting des journaux » s’applique directement.

Pour aller un cran plus loin, envisagez aussi d’écrire dans le journal des événements Windows. Contrairement à un journal fichier, son avantage est que le succès ou l’échec atteint un endroit que l’exploitation consulte déjà (Observateur d’événements, outils de surveillance existants).

# --- À exécuter une seule fois, lors de la configuration, avec des droits administrateur (dans un programme d'installation ou un script de configuration initiale) ---
if (-not [System.Diagnostics.EventLog]::SourceExists('KS-Jobs')) {
    [System.Diagnostics.EventLog]::CreateEventSource('KS-Jobs', 'Application')
}

# --- Le corps du job (exécuté avec le compte à privilège minimal) ne fait qu'écrire.
#     SourceExists peut exiger des droits administrateur pour parcourir tous les journaux ; ne l'appelez donc jamais à l'exécution.
#     Le bloc ci-dessous est censé être appelé dans le gestionnaire d'échec (catch) de Main ---
catch {
    $err = $_
    try {
        [System.Diagnostics.EventLog]::WriteEntry('KS-Jobs',
            "Cleanup-Logs failed: $($err.Exception.Message)",
            [System.Diagnostics.EventLogEntryType]::Error, 1001)
    }
    catch {
        # Ne laissez pas un échec d'écriture dans le journal justifier d'étouffer l'échec d'origine
        Write-Warning "Échec de l'écriture dans le journal des événements : $_"
    }
    exit 1
}

Deux précisions. Premièrement, l’enregistrement d’une source d’événements (CreateEventSource) nécessite des droits administrateur. Si vous mélangez cet enregistrement au corps du job, le premier échec en production — sous le compte de service à privilège minimal — se transforme en double échec : la tentative d’enregistrement lève une exception, et l’événement qui compte vraiment n’est jamais écrit. Comme montré ci-dessus, séparez l’enregistrement côté configuration, et faites en sorte que l’exécution ne fasse qu’écrire. Deuxièmement, si vous utilisez pwsh.exe comme moteur d’exécution, comme dans les exemples d’enregistrement de tâche de cet article, les applets de commande New-EventLog / Write-EventLog de l’ère Windows PowerShell 5.1 ne sont pas disponibles (la commande est introuvable et tout le script échoue). Appeler directement la classe .NET, comme ci-dessus, fonctionne aussi bien en 5.1 qu’en 7.

Au-delà de cela, mettre en place un mécanisme garantissant qu’« un échec parvient à un humain » — notification par e-mail ou vers Teams / Slack — évite l’incident où l’on ne découvre qu’à l’occasion d’un inventaire qu’une tâche était arrêtée depuis plusieurs mois. Nul besoin d’une plateforme de notification élaborée : quelques lignes qui font un POST vers un webhook uniquement en cas d’échec suffisent amplement. À l’inverse, « notifier à chaque succès » finit tôt ou tard par ne plus être lu : mieux vaut limiter les succès à un résumé hebdomadaire par exemple, et cibler la détection sur les échecs et sur « la tâche ne s’est pas exécutée » (une heure de dernière exécution devenue ancienne). Les critères de ce qu’il faut consigner dans un journal sont également détaillés dans « Capturer les exceptions et concevoir la journalisation ».

7. Contrôle des exécutions multiples et des traitements longs

Que se passe-t-il si la prochaine heure planifiée arrive alors que l’exécution précédente traîne encore en longueur ? Cela est déterminé par le réglage « Règle appliquée si la tâche est déjà en cours d’exécution », dans l’onglet Paramètres, qui correspond à -MultipleInstances en PowerShell.5

Valeur Comportement Convient à
IgnoreNew (valeur par défaut de l’interface : ne pas démarrer de nouvelle instance) Ignore le nouveau démarrage si une exécution est déjà en cours Les traitements par lots planifiés idempotents en général. À privilégier en premier
Queue Exécute la nouvelle instance dans l’ordre une fois la précédente terminée Les traitements d’agrégation où aucune perte n’est tolérée
Parallel Démarre en parallèle À éviter en principe. Uniquement si la sécurité en parallèle est garantie

Pensez également à régler « Arrêter la tâche si elle s’exécute plus de » (-ExecutionTimeLimit, 3 jours par défaut) sur une valeur réaliste (environ 2 à 3 fois la durée d’exécution attendue), afin qu’un processus bloqué n’entraîne pas le job du lendemain dans sa chute.5

Il faut faire attention au fait qu’IgnoreNew et Queue ne protègent qu’à l’intérieur de la même définition de tâche. Ils ne préviennent pas un conflit dans le cas où une autre tâche appelle le même script, ou lorsqu’une personne déclenche une exécution manuelle en réponse à un incident. S’il existe plusieurs chemins qui touchent à la même ressource (fichier, base de données, système externe), le script lui-même doit posséder son propre mécanisme d’exclusion mutuelle. L’approche classique est un mutex nommé.

$mutex = New-Object System.Threading.Mutex($false, 'Global\KS-Cleanup-Logs')
if (-not $mutex.WaitOne(0)) {
    Write-Warning 'Arrêt car une autre instance est déjà en cours d''exécution.'
    exit 0   # utilisez 0 si « ne pas avoir tourné » ne doit pas être considéré comme un échec, une valeur non nulle sinon
}
try {
    # traitement principal
}
finally {
    $mutex.ReleaseMutex()
}

Préfixer le nom avec Global\ rend l’exclusion mutuelle effective même entre sessions différentes (par exemple entre la tâche d’un autre utilisateur et une exécution manuelle). Le choix d’attendre le verrou (en passant un délai à WaitOne) ou d’abandonner immédiatement doit se décider selon la nature du job. Notez aussi qu’un objet nommé avec Global\ est visible par n’importe qui sur la machine. Sur un serveur partagé où plusieurs utilisateurs se connectent, si un mutex de même nom est saisi en premier — par malveillance ou par accident —, le job peut se retrouver ignoré indéfiniment (et, s’il se termine avec exit 0, paraître parfaitement sain en le faisant). Dans un tel environnement, configurez soit une ACL (MutexSecurity) sur le mutex pour restreindre les comptes autorisés à l’acquérir, soit, à défaut, faites remonter le fait d’avoir « échoué à l’acquérir et donc sauté » vers les notifications et le journal des événements de la section précédente, afin qu’une série d’exécutions sautées puisse être détectée par la surveillance. Le contrôle d’exclusion mutuelle pour les intégrations basées sur des fichiers est traité en détail dans « Bonnes pratiques pour l’intégration de fichiers et le verrouillage ».

8. Quand arrêter d’utiliser le Planificateur de tâches — la bascule vers un service résident

Le Planificateur de tâches n’est pas un outil universel. À mesure que les besoins évoluent, il existe un seuil au-delà duquel il vaut mieux changer de mécanisme que de forcer l’existant à continuer de fonctionner.

Besoin Mécanisme adapté
Traitement par lots planifié, jusqu’à quelques fois par jour Planificateur de tâches
Déclencheurs mêlant personnes, événements et horaires, avec besoin de visualiser l’ensemble du flux Power Automate (article séparé)
Scrutin à la minute, surveillance continue, traitement de files d’attente Service Windows / processus résident
Besoin de conserver un état entre les traitements, ou de contrôler finement les nouvelles tentatives et le backoff Service Windows / processus résident

Dès que vous commencez à faire du scrutin avec une « tâche toutes les 5 minutes », chaque démarrage engendre un coût de création de processus et de chargement de modules, et vous avez en plus besoin d’un mécanisme pour sauvegarder l’état précédent dans un fichier ou ailleurs — vous finissez de fait par réimplémenter un processus résident, mais de façon fragmentée. Arrivé à ce stade, le plus naturel est de le rendre résident avec le Generic Host de .NET et BackgroundService. Nous détaillons ce schéma d’implémentation dans « Utiliser Generic Host et BackgroundService dans une application de bureau ».

À l’inverse, transformer un traitement par lots mensuel ou quotidien en service pour gérer soi-même une minuterie est également excessif. Une règle qui ne trompe guère : le Planificateur de tâches convient si « l’intervalle est de l’ordre de l’heure ou plus, le traitement est indépendant et ne conserve pas d’état » ; dès que vous commencez à sortir de ce cadre, envisagez de passer en résident.

9. Liste de vérification avant la mise en exploitation

Avant l’enregistrement, nous recommandons de vérifier une fois chacun des points suivants.

  • Avez-vous déterminé le compte d’exécution (choisissez-vous SYSTEM par habitude ? Avez-vous envisagé un gMSA si vous êtes en domaine) ?
  • Avez-vous compris les contraintes du type de connexion (pas d’accès réseau en S4U ; en Password, avez-vous décidé de la conduite à tenir lors d’un changement de mot de passe) ?
  • Avez-vous testé le script séparément, dans des conditions équivalentes à celles du compte d’exécution ?
  • L’appelez-vous sous la forme -NoProfile -NonInteractive -ExecutionPolicy Bypass -File ?
  • Les chemins internes au script sont-ils basés sur $PSScriptRoot / UNC (sans dépendre de lecteurs mappés ou de chemins relatifs) ?
  • Avez-vous évité de mettre des guillemets dans « Commencer dans (facultatif) » ?
  • Avez-vous conçu les codes de sortie (0 pour succès / non nul pour échec ; avez-vous vérifié les conventions de code de sortie des commandes externes) ?
  • Avez-vous activé l’historique des tâches ? Le script dispose-t-il de sa propre journalisation et de notifications d’échec ?
  • Avez-vous explicitement défini les conditions d’alimentation, StartWhenAvailable, la gestion des exécutions multiples et la limite de temps d’exécution ?
  • Avez-vous enregistré la définition de la tâche sous forme de XML ou de script PowerShell dans un dépôt ?

10. Résumé

Le Planificateur de tâches n’est pas un dispositif où « écrire le script suffit » : une exploitation stable ne s’obtient qu’une fois conçus trois éléments de base — le compte d’exécution, la session et les valeurs par défaut. Dit autrement, une fois que vous avez pris en compte, au moment de l’enregistrement, les points soulevés dans cet article — le choix du type de connexion, l’activation de l’historique, la conception des codes de sortie et de la journalisation, la définition explicite du contrôle des exécutions multiples et de la limite de temps —, la suite demande étonnamment peu d’entretien.

« Ça marche à la main mais pas en exécution planifiée » est presque toujours dû à une différence de session et d’environnement. Avant de modifier les réglages au hasard, vérifiez jusqu’où l’exécution est allée dans l’onglet Historique, puis essayez les étapes d’isolation de cet article dans l’ordre.

Articles connexes

Domaines de conseil associés

Komura Software LLC propose des revues de conception pour l’automatisation métier basée sur PowerShell et le Planificateur de tâches, ainsi que des consultations pour remettre à plat des jobs planifiés tombés dans l’état « ça tourne, mais plus personne ne sait le réparer ».

Références

  1. Microsoft Learn, logonType Simple Type (Task Scheduler). Définitions des types de connexion, et sur le fait que S4U ne stocke pas le mot de passe mais perd en contrepartie l’accès aux ressources réseau et aux fichiers chiffrés.  2 3

  2. Microsoft Learn, Troubleshoot issues with scheduled tasks not running. Sur la démarche d’isolation — test du script seul → vérification de l’état/historique → changement des options de sécurité — et sur l’emplacement du journal d’événements TaskScheduler Operational.  2 3

  3. Microsoft Learn, Task Scheduler error and success constants. Sur les définitions des codes d’état et d’erreur tels que SCHED_S_TASK_READY (0x41300), SCHED_S_TASK_RUNNING (0x41301) et SCHED_S_TASK_HAS_NOT_RUN (0x41303).  2

  4. Microsoft Learn, Manage group Managed Service Accounts. Sur le fait que le mot de passe d’un gMSA peut être géré automatiquement côté domaine, et que les tâches du Planificateur de tâches prennent en charge l’exécution sous un gMSA.  2

  5. Microsoft Learn, New-ScheduledTaskSettingsSet. Sur les paramètres de l’objet de configuration de tâche, notamment MultipleInstances (Parallel / Queue / IgnoreNew), StartWhenAvailable et ExecutionTimeLimit (3 jours par défaut).  2 3 4 5

  6. Microsoft Learn, Security Contexts for Tasks. Sur le contexte de sécurité d’une tâche, et sur le fait que l’exécution d’une tâche enregistrée en Password / S4U requiert le droit « Ouvrir une session en tant que tâche par lots ». 

  7. Microsoft Learn, What’s New in Task Scheduler. Sur le fait que, à partir de Windows 10, le déclenchement des tâches non interactives est différé tant que le mode d’économie de batterie est actif. 

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Pourquoi mon script fonctionne-t-il manuellement mais échoue-t-il lorsqu'il est lancé depuis le Planificateur de tâches ?
C'est presque toujours dû à une différence de compte d'exécution, de session et d'environnement. Si vous choisissez « Exécuter que l'utilisateur soit connecté ou non », la tâche s'exécute dans une session non interactive : les lecteurs réseau mappés et les variables d'environnement définies par le profil utilisateur n'existent pas. De plus, avec « Ne pas stocker le mot de passe » (S4U), l'accès aux ressources réseau est impossible. Pour isoler la cause, la démarche efficace consiste à vérifier l'onglet Historique, à tester le script séparément, puis à basculer temporairement sur « Exécuter uniquement si l'utilisateur est connecté ». L'historique des tâches étant désactivé par défaut, veillez à l'activer avant la mise en exploitation.
Que signifie le 0x1 affiché dans le « Dernier résultat d'exécution » du Planificateur de tâches ?
0x1 n'est pas une erreur du Planificateur de tâches lui-même : cela signifie que le programme lancé a renvoyé le code de sortie 1, donc la cause se trouve du côté du script. Les codes de la famille 0x413xx sont des codes d'état du Planificateur de tâches, les codes 0x8007xxxx sont des codes d'erreur Windows (0x8007010B est un dossier de démarrage invalide, causé de façon classique par des guillemets dans ce champ, et 0x8007052E est un échec de connexion), et les petites valeurs comme 0x1 ou 0x2 sont des codes de sortie du programme lancé. Distinguer ces catégories évite de chercher au mauvais endroit dès le départ.
Quelle est la bonne façon d'appeler un script PowerShell depuis le Planificateur de tâches ?
Indiquez pwsh.exe (ou powershell.exe pour Windows PowerShell) comme programme, avec des arguments de la forme -NoProfile -NonInteractive -ExecutionPolicy Bypass -File suivi du chemin complet du script entre guillemets, et placez le dossier de travail dans « Commencer dans (facultatif) » sans guillemets. Utilisez -File plutôt que -Command pour que le code de sortie du script devienne le code de sortie du processus, basez les chemins internes au script sur $PSScriptRoot, et faites en sorte que le script renvoie explicitement 0 en cas de succès et une valeur non nulle en cas d'échec, avec $ErrorActionPreference réglé sur Stop.
Comment éviter qu'une tâche planifiée ne meure silencieusement après un changement de mot de passe ?
Une tâche enregistrée avec « Exécuter que l'utilisateur soit connecté ou non » stocke le mot de passe au moment de l'enregistrement ; un changement de mot de passe de domaine fait donc échouer toutes les connexions suivantes avec 0x8007052E, souvent sans que personne ne s'en aperçoive. En environnement de domaine, la meilleure réponse est un gMSA (compte de service géré par groupe), dont le mot de passe est géré automatiquement par le contrôleur de domaine, et le Planificateur de tâches prend en charge l'exécution sous un tel compte. Si vous utilisez plutôt un compte utilisateur standard dédié, gérez ensemble l'expiration du mot de passe et l'inventaire de vos tâches. Notez que l'option « Ne pas stocker le mot de passe » (S4U) évite le mot de passe stocké mais fait perdre l'accès aux ressources réseau et aux fichiers chiffrés EFS.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog