Ce fichier bat, faut-il le migrer vers PowerShell ? — Inventaire des actifs cmd/bat et critères de décision

· · PowerShell, Windows, Fichier batch, cmd, VBScript, Réutilisation d'actifs existants, Amélioration opérationnelle, Script

« À l’occasion d’une migration de serveur, on a retrouvé une montagne de fichiers batch vieux de vingt ans. Faut-il tout réécrire en PowerShell ? » — cette question revient systématiquement dans les consultations sur les actifs hérités. Copies et sauvegardes, wrappers de lancement d’applications, tâches d’intégration nocturnes : les batchs cmd (bat) soutiennent encore silencieusement l’exploitation Windows sur le terrain.

Pour donner la conclusion d’emblée : non, il n’est pas nécessaire de tout réécrire. Rien n’indique que le bat cessera bientôt de fonctionner, et réécrire quelque chose qui fonctionne est en soi un risque de panne. En revanche, il existe bel et bien des fichiers bat pour lesquels « laisser tel quel » est dangereux : ceux qui appellent VBScript, ceux qui étouffent les erreurs, ceux qui vont être modifiés prochainement. Ne pas faire ce tri, que ce soit en « tout garder » ou en « tout migrer », est source d’incidents dans les deux cas.

Cet article s’adresse aux responsables informatiques et aux équipes d’exploitation des PME. Il présente les critères pour inventorier les actifs bat et décider de leur migration, les pièges propres au bat, la manière de faire coexister bat et PowerShell pendant la période de transition, ainsi qu’un tableau de correspondance pour la réécriture.

1. La conclusion, d’abord

  • cmd.exe et bat ne sont pas voués à disparaître, mais Microsoft déclare clairement recommander PowerShell pour l’automatisation de Windows. Pour tout nouveau développement, PowerShell est l’orientation officielle.1
  • VBScript a fait l’objet d’une annonce officielle de dépréciation en octobre 2023, avec un plan par étapes publié : passage en fonctionnalité à la demande (Feature on Demand), puis suppression du système d’exploitation. Les actifs qui appellent VBScript depuis un bat via cscript / wscript forment le groupe où l’urgence de migration est la plus élevée.23 PowerShell est officiellement présenté comme le remplaçant.4
  • La décision de migration n’est pas un « tout ou rien », c’est un tri. Fonctionnement stable, aucune modification prévue → on garde. Modification à venir, gestion d’erreurs nécessaire, journalisation nécessaire → on migre. Dépendance à VBScript → migration prioritaire. Le tableau de décision du chapitre 4 organise tout cela.
  • Les faiblesses structurelles du bat sont l’absence d’arrêt sur erreur, les pièges de %ERRORLEVEL%, et l’encodage des caractères. if errorlevel 1 est un test « supérieur ou égal à 1 »5, et %errorlevel% se casse dès qu’une variable d’environnement du même nom est définie.5
  • Ce que l’on gagne en migrant vers PowerShell, c’est le pipeline d’objets, la gestion des erreurs avec try/catch6, les répétitions à blanc avec -WhatIf7, et les tests avec Pester. « Pouvoir remarquer les échecs, essayer en toute sécurité, tester automatiquement » constitue la valeur essentielle de la migration.
  • Pour la période de coexistence, la solution réaliste consiste à appeler powershell.exe / pwsh depuis le bat avec -File. Le code de sortie renvoyé par exit dans le script est transmis tel quel à %ERRORLEVEL% côté bat, ce qui permet de remplacer le contenu tout en conservant la gestion des tâches existante.89
  • N’allez pas réécrire des commandes externes éprouvées comme robocopy : appelez-les telles quelles depuis PowerShell. Le code de sortie va de 0 à 8 et plus, selon un schéma singulier où 8 et plus signifie un échec ; il suffit donc de bien écrire le test sur $LASTEXITCODE.1011
  • Intégrez la politique d’exécution dans la conception de la distribution. Par défaut, c’est Restricted (exécution de script interdite) sur les systèmes clients avec Windows PowerShell 5.1, et RemoteSigned sur Windows Server et PowerShell 7. Même sous RemoteSigned, les scripts non signés portant la marque « provenant d’Internet » sont bloqués.12

2. Pourquoi y réfléchir maintenant — le cmd reste, le VBScript disparaît

Commençons par poser les faits. Windows dispose de deux familles d’interpréteurs de commandes, cmd (l’interpréteur de commandes) et PowerShell, et la documentation officielle de Microsoft indique explicitement que « pour l’automatisation Windows la plus robuste et la plus moderne, l’usage de PowerShell est recommandé plutôt que les commandes Windows ou Windows Script Host ».1 Mais une recommandation reste une recommandation : cmd lui-même ne figure pas dans la liste des fonctionnalités obsolètes de Windows. À l’heure actuelle, rien n’indique que les fichiers bat existants cesseront de fonctionner.

VBScript présente un cas contrasté. En octobre 2023, la dépréciation de VBScript a été annoncée officiellement. Un plan a été publié selon lequel les futures versions de Windows le proposeront sous forme de fonctionnalité à la demande (Feature on Demand) avant de le supprimer du système d’exploitation.2 Comme il est d’abord fourni préinstallé, rien ne s’arrête du jour au lendemain3, mais l’orientation — la suppression — est actée. Windows Server 2025 a lui aussi basculé VBScript en FoD, avec la migration vers PowerShell officiellement conseillée comme remplacement.4

Précisons le calendrier avec exactitude. En juillet 2026, ce que Microsoft écrit dans sa documentation officielle se limite à l’ordre des étapes : « fourni sous forme de FoD, puis supprimé dans une future version de Windows » ; aucune date de suppression n’est indiquée.23 Autrement dit, à la question « il reste combien d’années ? », il n’existe pour l’instant pas de réponse officielle. Cela ne signifie pas « c’est encore loin, on peut laisser filer » : cela veut plutôt dire que si l’on commence à enquêter seulement une fois l’échéance annoncée, il sera trop tard. En bouclant seulement l’inventaire (chapitre 7) à l’avance, vous pourrez répondre instantanément, le jour où la date de suppression sera annoncée : « chez nous, les scripts concernés sont ces trois-là ».

Le point important ici, c’est que VBScript se cache à l’intérieur des actifs bat. La construction consistant à appeler VBScript depuis un bat via cscript //nologo convert.vbs était un motif classique des années 2000. Un bat construit de cette façon n’est pas « sûr parce que c’est du bat » — il partage le destin de VBScript. Lors de l’inventaire, vérifiez systématiquement non seulement les fichiers bat eux-mêmes, mais aussi ce qu’ils appellent (le chapitre 7 fournit un script d’analyse). Un examen complet de l’ensemble des actifs VBScript et VBA de l’entreprise est détaillé dans « Guide d’audit VBA et outils internes en prévision de l’abandon de VBScript ».

3. Les pièges du bat, et ce que PowerShell apporte

Comme élément de décision pour la migration, examinons concrètement les faiblesses structurelles du bat. Le batch suivant est une forme que l’on rencontre couramment pour les sauvegardes.

@echo off
rem Sauvegarde nocturne (le genre de batch risqué que l'on croise partout)
xcopy C:\data \\backup01\share\data /E /Y
echo Sauvegarde terminée >> C:\logs\backup.log

Trois problèmes difficiles à repérer se cachent dans ce batch.

  • Il ne s’arrête pas en cas d’erreur. Si xcopy n’atteint pas le partage de destination et échoue, cmd passe par défaut à la ligne suivante, et le journal enregistre « Sauvegarde terminée ». Pour détecter les échecs, il faut écrire soi-même, à chaque fois, un test if errorlevel.
  • %ERRORLEVEL% comporte des pièges. if errorlevel 1 ne signifie pas « le code de sortie est égal à 1 » mais « supérieur ou égal à 1 ».5 Par ailleurs, l’écriture %errorlevel% est spécifiée comme « se développe vers le code de sortie courant si aucune variable d’environnement nommée ERRORLEVEL n’est définie » ; dès qu’une personne écrit set ERRORLEVEL=0, tous les tests suivants sont cassés.5 Combiné au mécanisme exit /b <valeur> qui renvoie le code de sortie13, bien écrire ce genre de test demande une connaissance non négligeable.
  • Les accidents d’encodage des caractères. Le cmd d’un environnement japonais évolue traditionnellement dans un monde de type Shift_JIS, et ce genre d’accident se produit encore aujourd’hui : un bat réenregistré en UTF-8 devient illisible, ou un chemin contenant des symboles se comporte mal. Pour une vue d’ensemble de ce problème, voir « Les jeux de caractères et les fins de ligne sous Windows - Les bases du mojibake et de CRLF/LF ».

En écrivant le même traitement en PowerShell, la gestion des échecs change de nature structurellement.

# Sauvegarde nocturne (squelette de la version PowerShell)
$ErrorActionPreference = 'Stop'   # faire pencher les erreurs du côté « arrêt »
try {
    # La source doit pointer vers « le contenu du dossier », C:\data\*. Passer 'C:\data' aurait
    # pour effet, à partir de la deuxième exécution, d'imbriquer data\data une fois le dossier de
    # destination déjà existant
    Copy-Item -Path 'C:\data\*' -Destination '\\backup01\share\data' -Recurse -Force
    Add-Content -Path 'C:\logs\backup.log' -Value "$(Get-Date -Format o) Sauvegarde terminée"
    exit 0
}
catch {
    # Ce qui a échoué et où peut être consigné comme information structurée
    Add-Content -Path 'C:\logs\backup.log' -Value "$(Get-Date -Format o) Échec : $($_.Exception.Message)"
    exit 1
}

try/catch capture les erreurs qui mettent fin à une instruction, et le nettoyage peut s’écrire dans finally.6 PowerShell dispose en outre de -WhatIf, un mécanisme commun qui affiche « ce qui aurait dû se produire » sans exécuter l’opération destructrice7, permettant ainsi de répéter en toute sécurité un script réécrit face à la production.

-WhatIf reste abstrait tant qu’on ne l’a pas vu à l’œuvre ; voici donc à quoi cela ressemble réellement. Dans l’exemple de la documentation officielle, exécuter Remove-Item Date.csv -WhatIf ne supprime pas le fichier ; seule la ligne suivante s’affiche.7

What if: Performing operation "Remove File" on Target "C:\ps-test\date.csv".

Comme une ligne s’affiche par élément ciblé, lorsqu’on écrit avec un caractère générique comme Remove-Item C:\logs\*.log -WhatIf, tous les fichiers qui seraient supprimés s’affichent avant l’exécution. Cela permet de vérifier, sans abîmer les données de production, que « dans ces conditions, seuls les éléments réellement visés sont concernés » (la langue des messages affichés dépend des paramètres de l’environnement). Le bat n’a pas de mécanisme équivalent : il ne reste que la méthode manuelle consistant à composer la commande avec echo et à la vérifier visuellement.

En ajoutant des tests Pester, on peut en outre continuer à vérifier automatiquement que « cela devrait fonctionner » (voir « Mettre en place des tests PowerShell avec Pester — Une méthode concrète pour rendre les scripts d’exploitation moins fragiles »). La conception de la gestion des erreurs et des reprises est approfondie dans « Gestion des erreurs et conception des reprises en PowerShell », publié en même temps que cet article.

Autrement dit, ce que l’on gagne en migrant n’est pas la nouveauté syntaxique, mais une qualité opérationnelle : pouvoir remarquer les échecs, essayer en toute sécurité, et se protéger grâce aux tests. Inversement, cette valeur profite peu à un bat aussi simple qu’un wrapper de lancement, où un échec se remarque de toute façon immédiatement. D’où la nécessité de trier.

4. Ne pas tout migrer — le tableau de décision pour le tri

Point Options (la recommandation est explicitement indiquée) Critère de décision
Fonctionnement stable, aucune modification prévue, simple Garder tel quel (recommandé) / Migrer Un wrapper de lancement d’application ou quelques lignes de copie peuvent rester. La réécriture elle-même est un risque nouveau
Appelle VBScript (cscript/wscript) Garder / Migrer en priorité (recommandé) Le plan dépréciation → FoD → suppression de VBScript a été officiellement annoncé. Laisser faire mène à une date d’abandon2
Une modification de spécification ou une nouvelle fonctionnalité arrive Modifier en restant en bat / Migrer puis modifier (recommandé) Le moment du changement est l’occasion idéale de migrer. Faire la refonte et la mise en place des tests en même temps
Détection des échecs, journalisation, nouvelles tentatives nécessaires Ajouter des tests au bat / Migrer (recommandé) Couvrir tous les cas avec if errorlevel est difficile à maintenir. C’est un domaine où try/catch et la journalisation structurée sont efficaces6
Tâche nocturne (lancée par le Planificateur de tâches) Statu quo / Envisager la migration (recommandé) L’exécution sans surveillance est précisément l’endroit où la gestion des échecs compte le plus. Revoir aussi le compte d’exécution et le problème « se termine par 0x1 » (détaillé au chapitre 7)
Une commande externe comme robocopy joue le rôle principal Réimplémenter avec des applets de commande / Garder la commande externe et l’appeler (recommandé) Réimplémenter une commande éprouvée est une mauvaise affaire. Ne faire passer en PowerShell que l’appel et le test10
L’auteur a quitté l’entreprise, le comportement est inconnu Ne pas y toucher / Consigner le comportement avant de décider (recommandé) Commencer par documenter les entrées, sorties et la planification actuelles. Ne pas réécrire ce que l’on ne comprend pas

Deux principes guident les cas de doute. Premièrement, la migration se fait « par ordre de valeur », pas « par ordre d’ancienneté ». Deuxièmement, si l’on touche à quelque chose, commencer par la lecture (inventaire et consignation).

5. La solution réaliste pour la période de coexistence — appeler PowerShell depuis le bat et transmettre le code de sortie

Après le tri, la plupart des environnements entrent dans une période de coexistence où « une partie reste en bat, une autre passe en PowerShell ». Ici, ce qui est pratique, c’est de conserver le bat comme point d’entrée (l’interface avec la gestion des tâches) et de ne remplacer que son contenu par PowerShell. Les planificateurs de tâches et les procédures d’exploitation continuent de pointer vers le chemin du bat, ce qui permet de moderniser le contenu sans rien casser autour.

@echo off
rem Bat d'entrée : appelle job.ps1 situé dans le même dossier et transmet le code de sortie
rem %~dp0 est le dossier où se trouve ce bat lui-même (une pratique standard, documentée officiellement)
rem Si une variable d'environnement laissée par un « set ERRORLEVEL=... » subsiste, elle masque
rem le véritable code de sortie dans %ERRORLEVEL% ; on la supprime donc avant l'appel pour
rem retrouver la valeur dynamique
rem Ne pas écraser la politique d'exécution ici. Spécifier -ExecutionPolicy s'applique à la
rem portée Process, prioritaire, et affaiblit silencieusement un AllSigned etc. configuré par l'administrateur
set "ERRORLEVEL="
powershell.exe -NoProfile -File "%~dp0job.ps1"
exit /b %ERRORLEVEL%

Résoudre l’emplacement du script avec %~dp0 plutôt que de dépendre du répertoire courant est exactement ce que la documentation officielle présente comme exemple d’appel depuis un fichier bat.9 Pour l’exécuter sous PowerShell 7, il suffit de remplacer powershell.exe par pwsh (5.1 et 7 s’installent séparément et peuvent coexister ; pour le choix entre les deux, voir « Différences entre Windows PowerShell 5.1 et PowerShell 7, et migration », publié en même temps que cet article).

La transmission du code de sortie s’organise ainsi :

  • Si le script se termine par exit 4, le code de sortie du processus devient 4, et peut être récupéré via %ERRORLEVEL% côté bat. Cette interaction est documentée officiellement avec un exemple concret.8
  • Lorsqu’il est appelé avec -File, un script qui meurt sur une exception non gérée se termine avec le code 1, et 0 en cas de fin normale. Pour éviter le cas « échec mais 0 », il est plus sûr que le script détermine lui-même le succès ou l’échec et fasse un exit explicite.914
  • Le résultat d’un appel à une commande externe depuis PowerShell est placé dans la variable automatique $LASTEXITCODE.11

Un bat construit autour de robocopy en est un bon exemple. robocopy est une commande éprouvée dotée de nouvelles tentatives (par défaut, pas moins d’un million, avec une attente de 30 secondes), de mise en miroir et de journalisation10, et il n’y a aucune raison de la réimplémenter avec Copy-Item. Son schéma de code de sortie est toutefois singulier : 0 signifie « rien à copier », 1 signifie « copie réussie », et 8 ou plus signale un échec.10 Appliquer la règle générale « tout ce qui n’est pas 0 est anormal » ferait passer une copie parfaitement normale pour un échec.

# Utiliser robocopy tel quel, et se contenter de bien écrire le test en PowerShell
# /r:2 /w:5 — le million de tentatives par défaut équivaut à un blocage de fait pour une tâche sans surveillance, il faut donc toujours le limiter
robocopy 'C:\data' '\\backup01\share\data' /MIR /R:2 /W:5 /NP /LOG+:'C:\logs\robocopy.log'

if ($LASTEXITCODE -ge 8) {
    Write-Error "Échec de robocopy (code de sortie : $LASTEXITCODE)"
    exit 1
}
Write-Host "Synchronisation terminée (code de sortie : $LASTEXITCODE)"  # 0 à 7 sont des issues de succès
exit 0

6. Tableau de correspondance pour la réécriture — le vocabulaire du bat en PowerShell

Voici le tableau de correspondance pour la réécriture proprement dite. Utilisez-le non comme une liste de substitution mécanique, mais comme un tableau comparatif répondant à la question « comment exprimer la même intention ? ».

Écriture en bat Pratique standard en PowerShell Remarque
copy / move / del / md Copy-Item / Move-Item / Remove-Item / New-Item D’abord répéter à blanc les opérations destructrices avec -WhatIf7
xcopy / robocopy Appeler robocopy tel quel Ne pas réimplémenter. Considérer 8 ou plus comme un échec via $LASTEXITCODE1011
for %%f in (*.csv) do ... Get-ChildItem *.csv \| ForEach-Object { ... } Ce sont des objets qui circulent, pas des chaînes de noms de fichiers
if errorlevel 1 goto :error try/catch + $ErrorActionPreference = 'Stop' Les commandes externes s’évaluent toujours par code de sortie6
set VAR=value $var = 'value' (les variables d’environnement se notent $env:VAR) On peut distinguer les variables d’environnement du processus et les variables
call :sub / goto function On peut attacher des types et une validation aux arguments
findstr Select-String Les lignes correspondantes reviennent sous forme d’objets, exploitables en aval
>> log.txt Start-Transcript / Add-Content Transcript est le moyen simple de capturer l’intégralité d’une exécution
rem #

Le vocabulaire de base côté PowerShell — trouver une applet de commande, le pipeline, les bonnes pratiques de confirmation — est rassemblé dans « Les bases des commandes PowerShell — Les opérations à connaître en premier et comment les utiliser en toute sécurité ».

7. Procédure de migration progressive — inventaire → classification → pilote → exécution en parallèle

Pour finir, organisons la démarche en quatre étapes.

(1) Inventaire. Commencer par une investigation en lecture seule. Repérer mécaniquement où se trouvent les fichiers bat et s’ils dépendent de VBScript.

# Inventaire des actifs bat : liste des emplacements et détection des appels à VBScript (investigation sûre, en lecture seule)
# Tout emplacement qui n'a pas pu être énuméré devient un trou dans l'inventaire ; on le consigne donc avec -ErrorVariable pour le signaler ensuite
$targets = Get-ChildItem -Path 'C:\', 'D:\jobs', '\\fileserver\scripts' -Recurse `
    -Include '*.bat', '*.cmd' -File -ErrorAction SilentlyContinue -ErrorVariable enumErrors

$readErrors = @()
$report = foreach ($file in $targets) {
    # S'il y a un appel à cscript/wscript/.vbs, le marquer « dépend de VBScript » comme signal d'alerte
    # Passer les chemins déjà énumérés avec -LiteralPath (un nom de fichier contenant des crochets,
    # comme [2026]job.bat, serait interprété comme un caractère générique par -Path et pointerait vers un autre fichier)
    # Les fichiers dont le contenu n'a pas pu être lu (verrouillage, etc.) sont eux aussi consignés dans $readErrors
    $vbs = Select-String -LiteralPath $file.FullName -Pattern 'cscript|wscript|\.vbs' -Quiet `
        -ErrorAction SilentlyContinue -ErrorVariable +readErrors
    [pscustomobject]@{
        Path          = $file.FullName
        LastWriteTime = $file.LastWriteTime
        UsesVBScript  = $vbs
    }
}
$report | Sort-Object UsesVBScript -Descending | Export-Csv 'C:\audit\bat-inventory.csv' -NoTypeInformation -Encoding UTF8

# Conserver la liste des emplacements qui n'ont pas pu être inspectés (s'il n'est pas vide, l'inventaire est incomplet)
# Inclure à la fois les emplacements dont l'énumération a échoué et les fichiers énumérés mais non lisibles
@($enumErrors) + @($readErrors) | ForEach-Object { $_.TargetObject } |
    Set-Content -Path 'C:\audit\bat-uninspected.txt'

Le fichier bat-inventory.csv obtenu est un tableau simple de trois colonnes seulement. Les lignes où UsesVBScript vaut True sont les candidates à la migration prioritaire, regroupées en tête grâce à Sort-Object. Voici un aperçu de son contenu (chemins et dates sont donnés à titre d’exemple) :

Path LastWriteTime UsesVBScript
\\fileserver\scripts\nightly\convert.bat 2009/04/13 18:22:31 True
D:\jobs\eod\export_shipping.bat 2014/11/07 9:41:02 True
D:\jobs\backup\copy_master.bat 2021/06/02 14:05:47 (vide)
C:\tools\launch_viewer.bat 2018/02/19 11:30:15 (vide)

La colonne UsesVBScript vide n’est pas une anomalie. Le paramètre -Quiet de Select-String a pour spécification de renvoyer $true en cas de correspondance, et $null (et non $false) en l’absence de correspondance.15 Trier selon « True ou vide » suffit en pratique, mais si vous souhaitez afficher explicitement False, convertissez d’abord en valeur booléenne avant de stocker, comme dans $vbs = [bool](Select-String ...) (le format d’affichage des dates dépend de la culture de l’environnement d’exécution).

Ces quatre lignes suffisent déjà à faire avancer nettement la décision. Les deux premières relèvent de « migration prioritaire » au chapitre 4, la troisième de « migrer si une modification est prévue », et le dernier wrapper de lancement peut « rester tel quel ». En pratique, il suffit d’ajouter à la main les colonnes origine du lancement (Planificateur de tâches / outil de gestion des tâches / manuel) et responsable, et ce tableau devient directement le registre du plan de migration. Pour les fichiers dont LastWriteTime remonte à plus de dix ans, mieux vaut, à titre de précaution, réserver d’abord du temps de lecture, en les considérant comme des candidats « dont l’auteur n’est probablement plus là ».

(2) Classification des risques. Appliquer les résultats de l’inventaire au tableau de décision du chapitre 4, en triant en « garder », « migrer » et « migrer en priorité (dépendance à VBScript) ». Consigner en même temps, pour chaque bat, son origine de lancement (Planificateur de tâches, outil de gestion des tâches, manuel) et le périmètre d’impact en cas d’échec. Un point à ne pas négliger ici est le problème « se termine par 0x1 » : la valeur 0x1 qui apparaît dans le « dernier résultat d’exécution » d’une tâche ne signifie pas une erreur du Planificateur de tâches lui-même, mais que le programme lancé a renvoyé le code de sortie 1, et la cause tient presque toujours à un « écart entre l’environnement d’exécution manuelle et celui de l’exécution planifiée ». Le cas typique est celui où, faute d’avoir renseigné le champ « Commencer dans (facultatif) » côté tâche, le répertoire courant devient C:\Windows\System32, ce qui casse un bat reposant sur des chemins relatifs. D’autres causes incluent l’absence, dans la session sans surveillance, de variables d’environnement propres au profil ou de lecteurs réseau mappés, ou encore une politique d’exécution différente. Pour la démarche de diagnostic, voir « Le Planificateur de tâches n’exécute pas la tâche ou se termine par 0x1 — Isoler la cause et concevoir une exploitation fiable ».

(3) Pilote. Choisir un script à faible impact et le réécrire selon l’approche du bat d’entrée présentée au chapitre 5. Intégrer à ce stade la politique d’exécution dans la conception. Par défaut sous Windows, c’est RemoteSigned : les scripts créés localement fonctionnent, mais les scripts non signés portant la marque « provenant d’Internet » sont bloqués.12 Le diagnostic à mener lorsque la distribution par dossier partagé se heurte à cela, ainsi que la conception de fond intégrant la signature, sont détaillés dans « Politique d’exécution PowerShell et signature de scripts », publié en même temps que cet article.

(4) Exécution en parallèle. Faire tourner l’ancien bat et le nouveau script côte à côte pendant une période donnée. Séparer les destinations d’écriture pour comparer les résultats, faire tourner d’abord la nouvelle version uniquement avec -WhatIf ou en journalisation seule, et prévoir un retour en arrière qui consiste simplement à repointer la tâche vers le bat — une fois tout cela mis en place, retirer l’ancien bat ne devrait plus jamais transformer la migration elle-même en incident.

8. En résumé

  • cmd et bat ne sont pas voués à disparaître, mais la recommandation officielle est PowerShell. VBScript, à l’inverse, dispose d’un plan annoncé dépréciation → FoD → suppression, ce qui fait du VBScript appelé depuis un bat la priorité de migration absolue.
  • La migration est un tri, pas un « tout ». On garde les bats qui tournent de façon stable sans changement, et l’on migre ceux qui vont être modifiés, ceux qui ont besoin de gestion d’erreurs et de journalisation, et ceux qui dépendent de VBScript.
  • Le bat ne s’arrête pas sur erreur, if errorlevel 1 est un test « supérieur ou égal à 1 », et %errorlevel% se casse dès qu’une variable d’environnement du même nom existe. PowerShell offre « remarquer, essayer, se protéger » grâce à try/catch, -WhatIf et Pester.
  • Pour la période de coexistence, la solution réaliste consiste à appeler powershell.exe -File (ou pwsh -File) depuis un bat d’entrée, et à transmettre le code de sortie via exit et %ERRORLEVEL%.
  • Ne réécrivez pas des commandes externes éprouvées comme robocopy : contentez-vous de bien écrire le test sur $LASTEXITCODE (8 ou plus signale un échec).
  • La démarche est : inventaire (en commençant par la lecture) → classification des risques → pilote → exécution en parallèle. N’oubliez pas non plus la conception de la politique d’exécution et de la distribution.

Articles connexes

Domaines de conseil associés

Chez KomuraSoft LLC, nous intervenons sur l’inventaire des actifs hérités où bat, VBScript et PowerShell coexistent et sur l’élaboration de plans de migration, sur la bascule des tâches nocturnes vers PowerShell avec la conception opérationnelle qui l’accompagne, et sur l’investigation des dysfonctionnements liés à une migration. Vous pouvez nous consulter même à partir du simple déchiffrage d’un batch dont l’auteur n’est plus là.

Liens de référence

  1. Microsoft Learn, Windows Commands. Sur le fait que Windows dispose de deux interpréteurs de commandes, l’interpréteur de commandes (cmd) et PowerShell, et sur la déclaration officielle recommandant PowerShell plutôt que les commandes Windows ou Windows Script Host pour l’automatisation Windows la plus robuste et la plus moderne.  2

  2. Microsoft Learn, Deprecated features for Windows client. Sur l’annonce, en octobre 2023, de la dépréciation de VBScript, et sur le plan prévoyant qu’il soit fourni comme fonctionnalité à la demande dans les futures versions de Windows avant d’être supprimé du système d’exploitation.  2 3 4

  3. Microsoft Learn, Resources for deprecated features. Sur le fait que la fonctionnalité à la demande VBScript est initialement préinstallée et utilisable sans interruption pendant la période de préparation au retrait.  2 3

  4. Microsoft Learn, Features removed or no longer developed in Windows Server. Sur le fait que VBScript est fourni comme FoD dans Windows Server 2025 et supprimé dans une version ultérieure, et sur la recommandation officielle d’utiliser PowerShell comme remplacement pour l’automatisation de tâches et les scripts.  2

  5. Microsoft Learn, if. Sur le fait que « if errorlevel <nombre> » est un test « supérieur ou égal », vrai lorsque le code de sortie du programme précédent est égal ou supérieur au nombre indiqué, et sur le fait que %errorlevel% est un développement qui suppose l’absence d’une variable d’environnement nommée ERRORLEVEL, renvoyant la valeur de celle-ci si elle est définie.  2 3 4

  6. Microsoft Learn, about_Try_Catch_Finally. Sur le fait que les blocs try/catch/finally gèrent les erreurs mettant fin à une instruction et celles mettant fin à un script, que catch peut spécifier un type d’exception, et que finally s’exécute qu’il y ait eu erreur ou non, ce qui permet de l’utiliser pour le nettoyage.  2 3 4

  7. Microsoft Learn, about_CommonParameters. Sur les paramètres d’atténuation du risque (-WhatIf/-Confirm) offerts par les applets de commande qui modifient le système ou les données, et sur le fait que -WhatIf n’affiche qu’une description de l’impact sans exécuter la commande.  2 3 4

  8. Microsoft Learn, about_Language_Keywords. Sur le fait que le mot-clé exit définit $LASTEXITCODE et correspond à %ERRORLEVEL% côté cmd.exe, sur l’exemple concret où un script exécuté avec pwsh -File et se terminant par exit 4 est observé comme valant 4 dans le %ERRORLEVEL% de l’appelant, et sur le fait que l’absence d’instruction exit se traduit par 0 en cas de fin normale et par 1 en cas d’exception non gérée.  2

  9. Microsoft Learn, about_Pwsh. Sur l’utilisation du paramètre -File de pwsh, l’exemple officiel d’usage de %~dp0 pour désigner le répertoire d’exécution lors d’un appel depuis un script batch, le fait que le code de sortie vaille 1 en cas d’erreur mettant fin au script sous -File, et le fait qu’une sortie via la commande exit fasse de ce nombre le code de sortie.  2 3

  10. Microsoft Learn, robocopy. Sur le schéma de codes de sortie de robocopy (0 = rien à copier, 1 = tous les fichiers copiés avec succès, 8 et plus = un ou plusieurs échecs), sur le fait que la valeur par défaut des tentatives soit /r:1000000 (un million) avec un temps d’attente par défaut de /w:30 secondes, et sur des options telles que la mise en miroir et la journalisation.  2 3 4 5

  11. Microsoft Learn, about_Automatic_Variables. Sur le fait que la variable automatique $LASTEXITCODE contient le code de sortie d’un programme natif ou d’un script, et sur la façon dont sa valeur est déterminée lors d’une exécution de script avec -File (1 en cas d’exception, la valeur donnée à exit, ou 0 en cas de fin normale).  2 3

  12. Microsoft Learn, about_Execution_Policies. Sur le fait que la politique d’exécution par défaut sous Windows est RemoteSigned, que RemoteSigned exige une signature d’un éditeur de confiance pour les scripts provenant d’Internet sans l’exiger pour les scripts créés localement, et sur la signification de chaque politique (Restricted, AllSigned, Bypass, etc.).  2

  13. Microsoft Learn, exit. Sur le fait que « exit /b <code de sortie> » met fin à un script batch et définit la valeur indiquée dans la variable d’environnement ERRORLEVEL, et sur le fait qu’elle devient le code de sortie du processus lorsqu’elle met fin à cmd lui-même. 

  14. Microsoft Learn, about_PowerShell_exe. Sur la spécification du paramètre -File dans powershell.exe de Windows PowerShell 5.1, la syntaxe %windir% pour transmettre des variables d’environnement depuis cmd.exe, et la gestion des codes de sortie. 

  15. Microsoft Learn, Select-String. Sur le fait que Select-String est une applet de commande qui recherche du texte par expression régulière, et sur le fait que, avec -Quiet, la valeur renvoyée n’est pas un objet MatchInfo mais $true en cas de correspondance trouvée et $null en l’absence de correspondance. 

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.

Faut-il migrer tous les fichiers batch de l'entreprise vers PowerShell ?
Non, migrer l'intégralité n'est pas recommandé. Pour un batch qui tourne de manière stable depuis des années sans modification prévue, la réécriture elle-même devient un nouveau risque de panne. Les candidats prioritaires sont ceux qui vont être modifiés prochainement, ceux qui ont besoin d'une gestion d'erreurs et de journalisation, et ceux qui appellent VBScript (cscript/wscript). Trier avec un tableau de décision et migrer progressivement les scripts les plus utiles est la pratique standard sur le terrain.
cmd.exe et les fichiers batch vont-ils être abandonnés ?
cmd.exe ne figure pas dans la liste des fonctionnalités obsolètes de Windows, et aucune annonce de suppression n'a été faite. Microsoft déclare cependant clairement recommander PowerShell plutôt que les commandes Windows ou WSH pour l'automatisation de Windows. VBScript, à l'inverse, a fait l'objet d'une annonce officielle de dépréciation en octobre 2023, avec un plan par étapes publié : il deviendra une fonctionnalité à la demande (Feature on Demand) dans les futures versions de Windows avant d'être retiré du système. Le bat lui-même continuera de fonctionner, mais le jour viendra à coup sûr où le VBScript appelé depuis vos fichiers bat cessera de fonctionner.
Comment appeler un script PowerShell depuis un fichier bat ?
Il faut passer le script à powershell.exe (ou pwsh.exe pour PowerShell 7) avec le paramètre -File. Pour un script situé dans le même dossier que le bat, résoudre l'emplacement avec %~dp0 — comme dans « powershell.exe -NoProfile -File "%~dp0job.ps1" » — est la forme d'écriture indiquée dans la documentation officielle. Notez que spécifier -ExecutionPolicy s'applique à la portée Process, prioritaire, et affaiblit la politique configurée par l'administrateur ; il vaut donc mieux ne pas l'ajouter côté bat et laisser la conception de la politique d'exécution de l'environnement s'appliquer. Si le script renvoie une valeur numérique avec exit, cette valeur est transmise telle quelle à %ERRORLEVEL% côté bat, ce qui permet de basculer le contenu vers PowerShell tout en conservant le dispositif existant de gestion des tâches.
Quels sont les pièges de %ERRORLEVEL% dans les fichiers bat ?
Le plus classique est que « if errorlevel 1 » signifie « vrai si le code de sortie est supérieur ou égal à 1 » (ce n'est pas un test d'égalité). Par ailleurs, %errorlevel% est un développement dynamique qui suppose qu'aucune variable d'environnement nommée ERRORLEVEL n'est définie ; si vous en définissez une vous-même avec set ERRORLEVEL=0, cette valeur sera ensuite toujours renvoyée. De plus, par défaut, le bat passe à la ligne suivante même quand une commande échoue, si bien qu'un échec dont on a oublié d'écrire le test est étouffé silencieusement. C'est la relative rareté de ce genre de pièges qui motive la migration vers PowerShell, doté d'une véritable gestion des erreurs.
Comment réécrire en PowerShell un batch qui utilise robocopy ?
La règle de l'art est de ne pas le réécrire. robocopy est une excellente commande externe dotée de fonctionnalités éprouvées de nouvelles tentatives, de mise en miroir et de journalisation, et on peut l'appeler telle quelle depuis PowerShell. La réimplémenter avec Copy-Item demande beaucoup d'efforts pour une fiabilité moindre. Le point de vigilance concerne l'interprétation du code de sortie : robocopy renvoie des valeurs allant de 0 à 8 et plus, où 8 et plus signifie un échec, et 1 une copie normale réussie. Côté PowerShell, il faut examiner $LASTEXITCODE et considérer que « 8 ou plus » signale une anomalie.
Je me suis heurté à la politique d'exécution en distribuant un script PowerShell. Que dois-je faire ?
La politique d'exécution par défaut (RemoteSigned) exige une signature pour les scripts marqués comme provenant d'Internet. Si la distribution via un dossier partagé se retrouve bloquée, commencez par confirmer la cause ; la solution durable consiste soit à mettre en place une signature, soit à uniformiser la politique par stratégie de groupe. Ajouter -ExecutionPolicy Bypass à l'appel depuis un fichier batch est une solution pratique, mais à n'utiliser qu'après avoir vérifié qu'il s'agit bien de l'état voulu par la conception de la politique de votre organisation.

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