Automatiser les traitements Excel et CSV avec PowerShell — recettes pratiques pour l'agrégation, le rapprochement et la génération de rapports

· · PowerShell, Windows, CSV, Excel, Automatisation, Efficacité opérationnelle, Script, Valorisation des actifs existants

« Chaque mois, j’ouvre dans Excel le CSV extrait du système central, je fais un tableau croisé dynamique, je mets en forme et j’envoie par e-mail. » « Je rapproche les listes issues de deux systèmes différents et je cherche les écarts à l’œil nu. » Nous recevons régulièrement ce genre de demandes pour ce type de tâche répétitive. Même si chaque occurrence ne prend que 30 minutes, répétée chaque semaine, chaque mois et par plusieurs personnes, elle finit par représenter un temps considérable sur une année, et le copier-coller manuel s’accompagne toujours de son lot d’erreurs.

PowerShell est un outil bien adapté à ce domaine. Il est intégré en standard à Windows (Windows PowerShell 5.1), et permet de lire un CSV comme un « tableau d’objets », puis d’enchaîner agrégation, rapprochement et sortie dans un même pipeline. Avec en plus le module communautaire ImportExcel, vous pouvez même générer des rapports xlsx sans avoir Excel installé.

Ce domaine comporte cependant des pièges propres à l’environnement japonais. Comme l’encodage de caractères par défaut diffère complètement entre Windows PowerShell 5.1 et PowerShell 7, le scénario « ça marchait sur mon PC, mais ça produit des caractères corrompus ailleurs » revient sans cesse. Cet article s’adresse aux services informatiques et aux équipes métier des PME et organise les recettes pratiques pour automatiser les traitements CSV et Excel avec PowerShell, en commençant par le piège de l’encodage des caractères.

Prérequis : cet article s’adresse aux services informatiques et aux équipes métier des PME, mais ce n’est pas un article d’introduction à PowerShell. Nous partons du principe que vous connaissez déjà les variables et le pipeline, la syntaxe de foreach et if, ainsi que la création et l’exécution d’un fichier de script (.ps1). Si ce n’est pas encore le cas, lisez d’abord « Les bases des commandes PowerShell — premières opérations à connaître et utilisation sûre ». Des notions de niveau intermédiaire ou supérieur apparaissent aussi, comme les tables de hachage (@{}), try/finally, ou la libération des références COM, mais nous précisons à chaque fois « pourquoi c’est écrit ainsi » là où elles apparaissent. L’environnement d’exécution visé couvre à la fois Windows PowerShell 5.1 et PowerShell 7 ; les endroits où le comportement diffère sont signalés au fur et à mesure.

1. La conclusion, d’abord

  • Précisez toujours -Encoding lors de la lecture et de l’écriture d’un CSV. L’encodage par défaut varie d’une commande à l’autre sous Windows PowerShell 5.1, tandis que PowerShell 7 utilise uniformément l’UTF-8 sans BOM : les deux valeurs par défaut sont totalement différentes.1
  • Sous 5.1, l’encodage par défaut d’Export-Csv est ASCII. Si vous oubliez -Encoding, le japonais est perdu dès l’enregistrement. De plus, sous 5.1, Import-Csv interprète un fichier sans BOM comme de l’UTF-8, si bien qu’un CSV en Shift_JIS lu tel quel produit des caractères corrompus.1
  • L’agrégation repose fondamentalement sur la combinaison de Group-Object (regroupement) et Measure-Object (somme, moyenne, minimum/maximum). La plupart des agrégations que vous faites habituellement avec un tableau croisé dynamique Excel peuvent être remplacées par ces deux commandes.23
  • Pour rapprocher deux CSV, utilisez Compare-Object s’il s’agit seulement de détecter un écart, et une table de hachage s’il faut aussi rapprocher des colonnes (jointure). Compare-Object indique via SideIndicator dans quel côté seulement une ligne existe.4
  • Pour lire et écrire des fichiers xlsx, le module communautaire ImportExcel est le premier choix. Il ne nécessite pas l’installation d’Excel et permet de créer des tableaux, d’appliquer une mise en forme et même des tableaux croisés dynamiques.5
  • Piloter Excel lui-même via COM est un dernier recours. Un défaut de libération des références (RCW) laisse facilement EXCEL.EXE en mémoire67, et de toute façon, Microsoft ne recommande ni ne prend en charge l’automatisation d’Office dans un environnement sans surveillance (service ou exécution planifiée).8
  • Avant de le déployer dans le Planificateur de tâches, fixez explicitement trois points : l’encodage, les chemins et l’environnement d’exécution. La grande majorité des cas où un script qui fonctionnait en exécution interactive se casse en exécution planifiée tiennent à ces trois points.18

2. Les bases d’Import-Csv et Export-Csv — le plus grand piège est l’encodage des caractères

Import-Csv lit un CSV comme un tableau où « une ligne = un objet, une colonne = une propriété ». La ligne d’en-tête devient les noms des colonnes, et tout le traitement qui suit peut s’écrire par nom de propriété.9 Export-Csv fait l’inverse : il écrit les colonnes d’un objet dans un CSV.10 Jusque-là, c’est simple. Le problème, c’est l’encodage des caractères.

Comme la documentation officielle le précise, l’encodage par défaut de Windows PowerShell 5.1 n’est pas cohérent d’une commande à l’autre.1 Voici sous forme de tableau la portée qui affecte la pratique en environnement japonais.

Opération Par défaut sous Windows PowerShell 5.1 Par défaut sous PowerShell 7
Export-Csv ASCII (le japonais est perdu)1 UTF-8 sans BOM10
Import-Csv (fichier sans BOM) Interprété comme UTF-81 UTF-8 sans BOM
Get-Content (fichier sans BOM) ANSI = Shift_JIS en environnement japonais1 UTF-8 sans BOM
Out-File et redirection (>) UTF-16LE (avec BOM)1 UTF-8 sans BOM

Autrement dit, sous 5.1, « lu avec Get-Content, le fichier passait bien en Shift_JIS, mais avec Import-Csv ça devient illisible » ou « après un Export-Csv, tout le japonais est devenu des ? » sont deux comportements parfaitement conformes aux spécifications. PowerShell 7 est cohérent avec un UTF-8 sans BOM uniforme1, mais cette fois, un CSV du système central livré en Shift_JIS produit des caractères corrompus s’il est lu avec l’encodage par défaut. La conclusion est unique : précisez -Encoding à la fois en lecture et en écriture.

# Lire un CSV en Shift_JIS produit par le système central
# Windows PowerShell 5.1 : Default = la page de codes ANSI du système (Shift_JIS en environnement japonais)
$orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding Default

# Sous PowerShell 7, on peut préciser un numéro de page de codes (932 = Shift_JIS)
# $orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding 932

# Écrire en UTF-8 avec BOM réduit les risques de caractères corrompus si le fichier est ouvert
# dans Excel par un double clic
# PowerShell 7 : UTF8 devient sans BOM, donc précisez explicitement utf8BOM pour avoir un BOM
$orders | Export-Csv -LiteralPath 'C:\data\orders_out.csv' -NoTypeInformation -Encoding utf8BOM
# Windows PowerShell 5.1 : la valeur utf8BOM n'existe pas. UTF8 donne déjà un BOM
# $orders | Export-Csv -LiteralPath 'C:\data\orders_out.csv' -NoTypeInformation -Encoding UTF8

À partir de PowerShell 6.2, -Encoding peut aussi être précisé par un numéro de page de codes (932) ou un nom enregistré, et à partir de 7.4, la valeur ansi est également disponible.1 Par ailleurs, -NoTypeInformation sert à supprimer la ligne #TYPE que 5.1 ajoute en tête de fichier ; à partir de PowerShell 6, elle n’est plus ajoutée par défaut, donc ce paramètre n’est plus nécessaire (mais le préciser ne provoque pas d’erreur).10 Dans un script destiné à fonctionner à la fois sous 5.1 et sous 7, mieux vaut le conserver par prudence.

Les pièges du format CSV lui-même (la perte des zéros en tête à l’ouverture dans Excel, les valeurs contenant des virgules ou des sauts de ligne, la protection contre les injections) sont traités en détail dans « Le CSV n’est pas « juste du texte » ». Pour les bases de l’encodage des caractères et des fins de ligne, consultez « Encodage des caractères et fins de ligne sous Windows ».

Pour que cet article reste utilisable sans avoir à consulter les liens ci-dessus, voici en trois points l’essentiel de ce qui y est délégué. Ne double-cliquez jamais sur un CSV pour l’ouvrir dans Excel afin de le vérifier (les zéros en tête disparaissent et les longs nombres passent en notation exponentielle : vous croiriez vérifier le contenu, mais vous verriez en fait des valeurs corrompues. Pour regarder le contenu, utilisez un éditeur de texte ou Import-Csv). Une valeur contenant une virgule, un saut de ligne ou un guillemet double doit être entourée de guillemets (Export-Csv s’en charge automatiquement, donc ne construisez jamais un CSV à la main par concaténation de chaînes). Une valeur commençant par = peut être interprétée comme une formule par le tableur (n’ouvrez pas sans précaution un CSV reçu de l’extérieur). Concernant les fins de ligne, un fichier provenant d’un système autre que Windows ou d’un équipement ancien peut utiliser LF seul ; Import-Csv le lit sans problème, mais si vous écrivez un traitement maison avec -split, ne partez pas du principe que ce sera toujours CRLF.

3. Agrégation — reproduire l’équivalent d’un tableau croisé dynamique avec Group-Object et Measure-Object

Une agrégation du type « nombre de lignes et montant total par service » se construit fondamentalement en regroupant avec Group-Object, puis en totalisant chaque groupe avec Measure-Object.23

Fixons d’abord l’entrée. La recette qui suit part du principe que vous avez un fichier orders.csv de ce genre (issu, par hypothèse, du système central, encodé en Shift_JIS).

OrderNo,Date,Dept,Customer,Amount
1001,2026/07/01,Ventes 1,Société A,120000
1002,2026/07/01,Ventes 2,Société B,80000
1003,2026/07/02,Ventes 1,Société C,45000
1004,2026/07/03,Administration,Société A,15000
1005,2026/07/03,Ventes 2,Société D,230000
# On lit le CSV en Shift_JIS avec PowerShell 7, donc on précise explicitement la page de codes 932
# (sous 7, -Encoding Default signifierait UTF-8 et produirait des caractères corrompus.
#  Pour une exécution sous 5.1, précisez Default)
$orders = Import-Csv -LiteralPath 'C:\data\orders.csv' -Encoding 932

# Calculer le nombre de lignes et le montant total par service.
# Toutes les valeurs d'Import-Csv sont des chaînes : le point clé est de convertir en [decimal]
# avant de sommer
# (le Sum de Measure-Object -Sum devient un Double, donc on additionne les montants
#  nous-mêmes en gardant le type decimal, pour ne pas perdre en précision sur les
#  grands totaux ou les décimales)
$summary = $orders | Group-Object -Property Dept | ForEach-Object {
    $total = [decimal]0
    foreach ($row in $_.Group) { $total += [decimal]$row.Amount }
    [pscustomobject]@{
        Dept  = $_.Name    # valeur de la clé de regroupement
        Count = $_.Count   # nombre de lignes
        Total = $total     # montant total
    }
}

$summary | Sort-Object -Property Total -Descending |
    Export-Csv -LiteralPath 'C:\data\summary.csv' -NoTypeInformation -Encoding UTF8

Pour les 5 lignes ci-dessus, voici les valeurs que contient $summary (après tri). Le fichier summary.csv contiendra ces 3 lignes, avec leur en-tête.

Dept Count Total
Ventes 2 2 310000
Ventes 1 2 165000
Administration 1 15000

Si vous recopiez ce script, vérifiez d’abord que vous obtenez bien ces 3 lignes. Si les montants ou les décomptes ne correspondent pas, la cause est soit une variation dans l’écriture du nom de service (espace pleine chasse, chiffres pleine chasse), soit le problème de conversion de type décrit ci-dessous.

Le piège classique, c’est que toutes les valeurs d’un CSV sont des chaînes de caractères. Import-Csv ne renvoie que des propriétés textuelles9, donc convertissez-les explicitement en [decimal] avant de les passer à Measure-Object -Sum en pensant qu’il s’agit de nombres. Oublier cette conversion peut malgré tout sembler fonctionner sous 5.1, ce qui donne souvent lieu à un incident découvert seulement après coup, quand les montants ne correspondent plus.

Measure-Object peut aussi calculer simultanément -Average, -Maximum et -Minimum en plus de -Sum.3 Par ailleurs, Group-Object -AsHashTable permet d’obtenir directement une table de hachage « clé → tableau des lignes de ce groupe », qui peut aussi servir pour le rapprochement décrit plus loin.2 L’usage de ces commandes ponctuelles est détaillé dans « Recueil de commandes PowerShell pratiques ».

4. Rapprochement — quand utiliser Compare-Object plutôt qu’une jointure par table de hachage

4.1. Compare-Object suffit s’il s’agit seulement de détecter un écart

Le rapprochement classique consistant à extraire « qui a été ajouté, qui a disparu » entre le CSV de la liste d’hier et celui d’aujourd’hui se fait le plus vite avec Compare-Object. En précisant la colonne clé avec -Property, la comparaison ne porte que sur les valeurs de cette colonne, et le SideIndicator du résultat indique l’ajout ou la suppression selon qu’il vaut => (présent uniquement du côté différence) ou <= (présent uniquement du côté référence).4

# On entoure de @() parce que, avec 0 ligne ou un fichier vide, le résultat d'Import-Csv
# peut valoir $null. Si ReferenceObject/DifferenceObject vaut $null, Compare-Object
# s'arrête avec une erreur terminale
$yesterday = @(Import-Csv -LiteralPath '.\users_0716.csv' -Encoding UTF8)
$today     = @(Import-Csv -LiteralPath '.\users_0717.csv' -Encoding UTF8)

# Comparaison uniquement sur le numéro d'employé. => : ligne présente seulement aujourd'hui (ajout),
# <= : ligne présente seulement hier (suppression)
Compare-Object -ReferenceObject $yesterday -DifferenceObject $today -Property EmpNo |
    Sort-Object -Property EmpNo |
    Format-Table -Property EmpNo, SideIndicator

Deux points de vigilance. Premièrement, quand vous précisez -Property, le résultat ne conserve que cette colonne et le SideIndicator ; si vous voulez aussi voir d’autres colonnes comme le nom, il faut retrouver les données d’origine à partir de la clé du résultat. Deuxièmement, si le côté référence ou le côté différence vaut $null (null, et non zéro ligne), l’opération s’arrête avec une erreur.4 C’est la raison pour laquelle le code ci-dessus entoure le résultat de la lecture avec @() : même un CSV de 0 ligne devient un tableau vide, ce qui permet de recevoir le résultat comme un écart plutôt que comme une erreur de format, même dans un cas extrême comme « tout le monde est parti aujourd’hui » (c’est-à-dire toutes les lignes sortant en <=).

4.2. Une table de hachage si vous devez aussi rapprocher des colonnes (jointure)

L’équivalent d’une jointure SQL — « associer au numéro d’employé du CSV de détail le nom et le service tirés du CSV maître » — se traite classiquement en transformant le fichier maître en table de hachage clé → ligne, puis en faisant une recherche ligne par ligne. Une double boucle (produit croisé détail × maître) devient visiblement lente dès quelques milliers de lignes de chaque côté, mais une table de hachage reste rapide même sur plusieurs dizaines de milliers de lignes.

Prenons ces deux entrées.

master.csv
EmpNo,Name,Dept
E001,Jean Dupont,Ventes 1
E002,Marie Martin,Administration

details.csv
EmpNo,Amount
E001,120000
E003,45000
# Transformer le fichier maître en table de hachage « numéro d'employé -> ligne »
# En cas de clé dupliquée, la ligne suivante écrase la précédente : vérifiez à l'avance
# si des doublons sont possibles
$master = @{}
foreach ($row in (Import-Csv -LiteralPath '.\master.csv' -Encoding UTF8)) {
    $master[$row.EmpNo] = $row
}

# Rapprocher le détail ligne par ligne. Ne pas faire disparaître silencieusement les lignes
# introuvables : les mettre de côté dans un fichier séparé
$unmatched = New-Object System.Collections.Generic.List[object]
$joined = foreach ($row in (Import-Csv -LiteralPath '.\details.csv' -Encoding UTF8)) {
    $hit = $master[$row.EmpNo]
    if ($null -eq $hit) {
        $unmatched.Add($row)
        continue
    }
    [pscustomobject]@{
        EmpNo  = $row.EmpNo
        Name   = $hit.Name
        Dept   = $hit.Dept
        Amount = $row.Amount
    }
}

$joined    | Export-Csv -LiteralPath '.\joined.csv'    -NoTypeInformation -Encoding UTF8
$unmatched | Export-Csv -LiteralPath '.\unmatched.csv' -NoTypeInformation -Encoding UTF8

Avec ces entrées, joined.csv contient la ligne E001 présente dans le fichier maître (E001,Jean Dupont,Ventes 1,120000), et unmatched.csv contient la ligne E003 absente du fichier maître (E003,45000).

Le cœur de cette recette tient dans les deux dernières lignes. Ne jetez jamais silencieusement une ligne dont la clé n’a pas pu être retrouvée. La valeur d’un travail de rapprochement réside dans le fait qu’une personne puisse vérifier ce qui n’a pas correspondu ; sortez donc toujours le fichier unmatched et affichez le nombre de lignes dans un journal ou en sortie standard. Dans l’exemple ci-dessus, remarquer que E003 apparaît dans unmatched.csv est précisément la raison d’être de ce traitement.

4.3. Variations des noms de colonnes et gestion des en-têtes

Sur le terrain, les noms de colonnes d’un CSV varient souvent (« 社員番号 », « 社員No », « emp_no »). La parade consiste, en tout et pour tout, à normaliser vers des noms internes juste après la lecture. En écrivant ensuite tout le traitement uniquement avec les noms internes, il suffit de corriger un seul point — la normalisation — même si le format change.

# Pour un CSV sans ligne d'en-tête, donnez les noms de colonnes avec -Header
# (la première ligne est alors lue comme une donnée)
# Encodage : même logique qu'au chapitre 2 -- 932 sous 7, Default sous 5.1 pour du Shift_JIS
$rows = Import-Csv -LiteralPath '.\no_header.csv' -Header 'EmpNo', 'Name', 'Dept' -Encoding 932

# Pour un CSV avec des en-têtes en japonais, normalisez vers des noms internes en alphabet
# juste après la lecture
$normalized = Import-Csv -LiteralPath '.\jinji.csv' -Encoding 932 |
    Select-Object -Property @{ Name = 'EmpNo'; Expression = { $_.'社員番号' } },
                            @{ Name = 'Name';  Expression = { $_.'氏名' } },
                            @{ Name = 'Dept';  Expression = { $_.'所属部署' } }

-Header est destiné aux fichiers sans ligne d’en-tête ; notez que, quand vous le précisez, la première ligne est aussi lue comme une donnée.9 Par ailleurs, si l’en-tête comporte une case vide, PowerShell lui attribue automatiquement un nom de colonne provisoire du type H19, donc si une propriété attendue ne se retrouve pas sous le nom de colonne prévu, soupçonnez d’abord la ligne d’en-tête.

5. Manipuler les fichiers xlsx — ImportExcel en premier choix, COM en dernier recours

5.1. Le module ImportExcel — lire et écrire des fichiers xlsx sans Excel installé

Dans les entreprises japonaises, on vous demande souvent le résultat d’une agrégation « pas en CSV, mais en fichier Excel, avec des couleurs sur le tableau ». C’est là que s’impose le premier choix : le module communautaire ImportExcel, publié sur PowerShell Gallery. Il permet de lire et d’écrire des fichiers xlsx sans avoir besoin d’installer Excel lui-même, et va jusqu’à créer des tableaux, ajuster la largeur des colonnes et générer des tableaux croisés dynamiques.5

# Une seule fois. Installer depuis PowerShell Gallery pour l'utilisateur courant
# (les droits administrateur ne sont pas nécessaires)
# Si Windows PowerShell 5.1 n'arrive pas à se connecter à la Gallery, activez d'abord TLS 1.2
# [Net.ServicePointManager]::SecurityProtocol =
#     [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Install-Module -Name ImportExcel -Scope CurrentUser

# Vérifier que le module est bien installé et sa version
Get-InstalledModule -Name ImportExcel | Select-Object -Property Name, Version

# Lire un xlsx (comme avec Import-Csv, le tableau de la feuille devient un tableau d'objets)
$budget = Import-Excel -Path 'C:\data\budget.xlsx' -WorksheetName 'Budget'

# Exporter le résultat d'agrégation du chapitre 3 en xlsx, avec tableau + largeur de colonnes
# automatique + tableau croisé dynamique
$summary | Export-Excel -Path 'C:\data\monthly-report.xlsx' `
    -WorksheetName 'Synthèse' -TableName 'Summary' -AutoSize `
    -IncludePivotTable -PivotRows Dept -PivotData @{ Total = 'Sum' }

Deux points bloquent souvent lors de l’installation. Le premier concerne la connexion à PowerShell Gallery : l’utilisation de la Gallery nécessite TLS 1.2 ou supérieur, donc sous Windows PowerShell 5.1, Install-Module peut échouer si vous ne l’activez pas explicitement pour la session, comme dans le commentaire ci-dessus (si l’écrire à chaque fois est fastidieux, placez cette ligne dans votre script de profil).11 Le second concerne la vérification de version : la version de PowerShell prise en charge par ce module n’est pas précisée sur la page de distribution.5 Si vous l’utilisez dans une entreprise où 5.1 et 7 coexistent, exécutez une fois Export-Excel avec la version de PowerShell réellement utilisée en production, vérifiez que le fichier s’ouvre, puis seulement déployez (si vous l’exécutez via le Planificateur de tâches, vérifiez avec le même compte d’exécution que celui utilisé par la tâche : un module installé avec -Scope CurrentUser n’est visible que pour cet utilisateur-là).

S’agissant d’un module communautaire, vérifiez son installation conformément aux règles internes de votre entreprise en matière d’installation de logiciels (le module provient de PowerShell Gallery5). Cela dit, comparé à une configuration « installer Excel sur le serveur et le piloter via COM », c’est un choix bien plus judicieux, que ce soit en matière de licence, de stabilité ou de maintenance. La comparaison des méthodes de génération de rapports (COM / Open XML / modèles) est présentée dans « Comment créer une sortie de rapport Excel ».

5.2. Automatisation COM d’Excel — si vous l’utilisez, allez jusqu’à la libération complète, et ne l’exécutez jamais sans surveillance

C’est seulement quand vous voulez déclencher une macro d’un fichier xls existant, ou quand vous avez besoin d’une fonctionnalité propre à Excel (recalcul, impression, résolution de noms définis, etc.) que vous devez piloter Excel lui-même via COM. Depuis PowerShell, vous pouvez le démarrer avec New-Object -ComObject, mais le problème bien connu, c’est le processus EXCEL.EXE qui reste actif. Si les références aux objets COM (RCW) ne sont pas libérées, le processus subsiste même après avoir appelé Quit().6 Côté .NET, la parade consiste à les libérer explicitement avec Marshal.ReleaseComObject.7

# COM est un dernier recours. Le finally garantit "toujours fermer ce qui a été ouvert,
# toujours libérer les références"
# Tous les appels COM après le lancement d'Excel se font dans le try (pour que le nettoyage
# s'exécute même si une exception survient dès la première ligne après le lancement)
$excel = New-Object -ComObject Excel.Application
$book = $null
try {
    $excel.Visible = $false
    $excel.DisplayAlerts = $false   # éviter un blocage sur une boîte de dialogue de confirmation
    $book  = $excel.Workbooks.Open('C:\data\template.xlsx')
    $sheet = $book.Worksheets.Item(1)
    $sheet.Range('B2').Value2 = 12345
    $book.SaveAs('C:\data\output.xlsx')
    $book.Close($false)
}
finally {
    # Même si Quit échoue (Excel ne répond plus, etc.), toujours exécuter la libération et le GC
    try {
        $excel.Quit()
    }
    finally {
        # Sans libération explicite du RCW, les objets COM manipulés laissent facilement
        # EXCEL.EXE en mémoire
        if ($sheet) { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($sheet) }
        if ($book)  { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($book) }
        [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel)
        [GC]::Collect()
        [GC]::WaitForPendingFinalizers()
    }
}

Ce qui complique les choses, c’est qu’un simple enchaînement de points comme $excel.Workbooks.Open(...) fait naître une référence à un objet intermédiaire (ici, la collection Workbooks), qui doit lui aussi être libéré. Dans le code ci-dessus, la référence à Workbooks subsiste techniquement ; pour être tout à fait rigoureux, il faudrait aussi capturer les objets intermédiaires dans des variables et les libérer. C’est précisément cette « difficulté à écrire une gestion des références jusqu’au bout » qui justifie de considérer COM comme un dernier recours. Les détails du mécanisme et les critères de remplacement sont traités dans « Le problème du processus EXCEL.EXE qui reste actif lors de la manipulation d’Excel en C# », et l’appel de COM et .NET depuis PowerShell en général est traité dans « Appeler COM et .NET depuis PowerShell en pratique », publié en même temps.

Il y a par ailleurs une contrainte décisive supplémentaire. Microsoft ne recommande pas et ne prend pas en charge l’automatisation d’Office depuis un client sans surveillance et non interactif (service, exécution planifiée, etc.). Office est conçu en partant du principe qu’un utilisateur interactif est présent, et il est officiellement précisé qu’un comportement instable ou un blocage (deadlock) peut survenir dans un environnement sans surveillance.8 La documentation SSIS recommande elle aussi, pour les environnements d’exécution sans surveillance, de remplacer la connexion Excel par un format CSV ou de type Open XML plutôt que de l’utiliser.12 Une configuration du type « ouvrir Excel chaque nuit via le Planificateur de tâches pour générer un rapport » est un château de cartes, même si elle semble fonctionner. Pour tout traitement destiné à une exécution planifiée, orientez-vous vers ImportExcel ou vers une méthode de type Open XML.

6. Liste de vérification avant une exécution planifiée avec le Planificateur de tâches

Si vous déployez tel quel dans le Planificateur de tâches un script qui fonctionnait sur votre poste, il se casse le plus souvent pour l’une des raisons suivantes.

  • Encodage des caractères : le comportement ne change pas entre exécution interactive et exécution planifiée, mais il est fréquent qu’un script qui « fonctionnait par hasard » sous la version 7 sur votre poste se casse parce que la commande de lancement de la tâche est powershell.exe (= 5.1), ce qui change l’encodage par défaut. Comme indiqué au chapitre 2, si vous précisez explicitement -Encoding en lecture et en écriture, le résultat sera le même quel que soit l’interpréteur qui démarre.1
  • Chemins : n’utilisez pas de chemin relatif dépendant du répertoire courant ; écrivez un chemin absolu ou basé sur $PSScriptRoot. Les lecteurs réseau (Z: par exemple) ne sont pas visibles dans la session d’une exécution planifiée : utilisez un chemin UNC.
  • Environnement d’exécution : précisez explicitement, dans la commande de lancement, quel interpréteur PowerShell exécute le script (powershell.exe ou pwsh.exe). Les différences entre 5.1 et 7 et comment choisir sont traitées dans « Les différences entre Windows PowerShell 5.1 et PowerShell 7 », publié en même temps.
  • Ne déployez jamais un traitement impliquant Excel COM : comme vu au chapitre précédent, l’exécution sans surveillance n’est pas prise en charge.8

La façon de conserver journaux et traces lors d’une exécution via le Planificateur de tâches est détaillée dans « PowerShell avancé — automatiser en toute sécurité l’investigation des journaux, l’archivage et la génération de rapports ».

7. Bonnes pratiques (tableau de décision)

Point Options Critère de décision
Encodage du CSV Laisser la valeur par défaut / Préciser -Encoding Toujours le préciser. Comme 5.1 et 7 ont des valeurs par défaut différentes, cela évite structurellement le « ça se corrompt quand l’environnement change »1
Agrégation À la main dans Excel / Group-Object + Measure-Object Si c’est répété chaque semaine ou chaque mois, scriptez-le. La procédure est conservée sous forme de code et devient reproductible23
Rapprochement de deux CSV Compare-Object / jointure par table de hachage Compare-Object s’il s’agit seulement de détecter un écart. Une table de hachage s’il faut rapprocher d’autres colonnes. Les lignes non concordantes doivent toujours être exportées à part4
Sortie xlsx S’en tenir au CSV / ImportExcel / COM ImportExcel s’il faut de la mise en forme ou un tableau croisé dynamique (Excel non requis). COM uniquement si une fonctionnalité propre à Excel, comme l’exécution d’une macro, est réellement nécessaire5
Mode d’exécution d’Excel COM Sans surveillance via le Planificateur de tâches / Exécution manuelle et interactive uniquement L’automatisation d’Office sans surveillance n’est pas prise en charge. Pour un traitement destiné à l’automatisation sans surveillance, orientez-vous vers une méthode ne nécessitant pas Excel8
Exécution planifiée À la main à chaque fois / Planificateur de tâches Précisez -Encoding, un chemin absolu et l’interpréteur PowerShell lancé avant de le déployer comme tâche

8. Conclusion

  • Le plus grand piège de l’automatisation CSV, c’est l’encodage des caractères. Sous 5.1, la valeur par défaut varie d’une commande à l’autre (Export-Csv utilise ASCII), tandis que 7 utilise uniformément l’UTF-8 sans BOM. Préciser -Encoding en lecture comme en écriture élimine les différences entre environnements.
  • L’agrégation repose fondamentalement sur Group-Object + Measure-Object. Comme toutes les valeurs d’un CSV sont des chaînes, convertissez-les en valeurs numériques avant d’agréger.
  • Pour le rapprochement, utilisez Compare-Object s’il s’agit seulement de détecter un écart, une table de hachage si une jointure est nécessaire. Sortez toujours dans un fichier séparé les lignes qui n’ont pas pu être rapprochées, pour qu’une personne puisse les vérifier.
  • Absorbez les variations de noms de colonnes par une normalisation juste après la lecture, puis écrivez tout le reste du traitement uniquement avec les noms internes.
  • Les fichiers xlsx peuvent être lus et écrits sans Excel installé grâce au module ImportExcel. COM est un dernier recours, à réserver aux cas où vous pouvez écrire la libération jusqu’au bout, et ne doit jamais servir à une exécution sans surveillance.
  • Avant de le déployer dans le Planificateur de tâches, fixez explicitement l’encodage, les chemins et l’interpréteur PowerShell lancé.

Articles connexes

Domaines de conseil associés

合同会社小村ソフト (Komura Software LLC) prend en charge la création de scripts pour automatiser les tâches répétitives passant par le CSV et Excel, l’abandon des traitements de rapports dépendants d’Excel COM (remplacement par ImportExcel ou Open XML), ainsi que l’investigation d’incidents tels que « des caractères corrompus uniquement dans certains environnements » ou « EXCEL.EXE qui reste en mémoire ».

Références

  1. Microsoft Learn, about_Character_Encoding. Sur le fait que l’encodage par défaut de Windows PowerShell 5.1 n’est pas cohérent d’une commande à l’autre (Export-Csv utilise ASCII, Set-Content/Get-Content utilisent l’ANSI par défaut, Out-File et la redirection utilisent l’UTF-16LE, et Import-Csv interprète un fichier sans BOM comme de l’UTF-8), que PowerShell 6 et versions ultérieures utilisent uniformément l’UTF-8 sans BOM par défaut, qu’à partir de 6.2 -Encoding peut être précisé par numéro de page de codes ou nom enregistré, et sur la valeur ansi de la version 7.4.  2 3 4 5 6 7 8 9 10 11 12

  2. Microsoft Learn, Group-Object. Sur le fait que Group-Object regroupe les objets selon la valeur d’une propriété donnée et renvoie le nombre et les éléments de chaque groupe, et que -AsHashTable permet d’obtenir une table de hachage clé → groupe.  2 3 4

  3. Microsoft Learn, Measure-Object. Sur le fait que Measure-Object peut calculer, en plus du nombre d’éléments, la somme (Sum), la moyenne (Average), le maximum et le minimum (ainsi que l’écart-type).  2 3 4

  4. Microsoft Learn, Compare-Object. Sur le fait que Compare-Object compare deux ensembles d’objets et indique, via SideIndicator (<= / => / ==), de quel côté seulement un élément existe, que -Property permet de ne comparer que les colonnes indiquées, et que le fait que le côté référence ou différence soit null provoque une erreur terminale.  2 3 4

  5. Microsoft Learn, An active Excel process continues to run after using a VBA macro to programmatically quit Excel. Sur le fait que, même après avoir fermé le classeur, appelé Quit et vidé les références, le processus EXCEL.EXE reste actif si une référence à Excel ou à l’un de ses membres est conservée globalement.  2

  6. Microsoft Learn, Marshal.ReleaseComObject(Object) Method. Sur le fait que ReleaseComObject décrémente le compteur de références du RCW (Runtime Callable Wrapper) associé à un objet COM et sert à contrôler explicitement la durée de vie de l’objet COM, et sur le fait qu’un usage attentif est nécessaire car accéder à un objet déjà libéré provoque une exception.  2

  7. Microsoft Learn, Considerations for unattended automation of Office in the Microsoft 365 for unattended RPA environment. Sur le fait que Microsoft ne recommande pas et ne prend pas en charge l’automatisation des applications Office depuis un client sans surveillance et non interactif (ASP, DCOM, service NT, etc.), que cela peut provoquer un comportement instable ou un blocage (deadlock) dans un environnement sans surveillance, et que des alternatives comme Open XML sont recommandées.  2 3 4 5

  8. Microsoft Learn, Import-Csv. Sur le fait qu’Import-Csv crée des objets personnalisés tabulaires à partir d’un CSV, que la première ligne est interprétée comme l’en-tête, que -Header permet de donner des noms de colonnes à un fichier sans en-tête, que les cases vides de l’en-tête reçoivent un nom de colonne provisoire commençant par H, et que les valeurs sont lues comme des chaînes de caractères.  2 3 4

  9. Microsoft Learn, Export-Csv. Sur le fait qu’Export-Csv écrit chaque propriété d’un objet comme une colonne du CSV, que l’encodage par défaut sous PowerShell 7 est UTF8NoBOM, et qu’à partir de PowerShell 6.0 la ligne #TYPE n’est plus produite par défaut, NoTypeInformation devenant implicite.  2 3

  10. Microsoft Learn, Install a package manager for PowerShell. Sur le fait que l’accès à PowerShell Gallery nécessite la sécurité de la couche transport (TLS) 1.2 ou supérieure, sur la commande permettant d’activer TLS 1.2 pour la session, et sur la possibilité de la placer dans un script de profil. 

  11. Microsoft Learn, Import data from Excel or export data to Excel with SQL Server Integration Services (SSIS). Sur le fait que l’utilisation d’un composant Excel n’est pas prise en charge dans un environnement sans surveillance et non interactif, et qu’il est recommandé de remplacer, pour les traitements automatisés en production, l’approche par des fichiers plats (CSV) ou de type Open XML. 

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 ai-je des caractères corrompus (mojibake) en lisant ou écrivant un CSV avec PowerShell ?
Parce que Windows PowerShell 5.1 et PowerShell 7 n'ont pas le même encodage de caractères par défaut. Sous 5.1, l'encodage par défaut varie selon la commande : Export-Csv utilise ASCII (le japonais est perdu), Import-Csv interprète un fichier sans BOM comme de l'UTF-8, et Get-Content utilise l'ANSI (le Shift_JIS dans un environnement japonais). Sous PowerShell 7, l'encodage par défaut est uniformément l'UTF-8 sans BOM. La seule parade fiable est de toujours préciser explicitement -Encoding, à la fois en lecture et en écriture.
Comment rapprocher (comparer les écarts entre) deux fichiers CSV ?
Si vous voulez seulement savoir s'il existe un écart, le plus simple est d'utiliser Compare-Object avec -Property pour désigner la colonne clé : SideIndicator indique les lignes ajoutées (=>) et les lignes disparues (<=). Si vous devez en plus rapprocher des colonnes comme le nom ou le service à partir de l'autre CSV, la méthode standard consiste à transformer le fichier maître en table de hachage clé → ligne, puis à faire une recherche ligne par ligne : cela reste rapide même sur plusieurs dizaines de milliers de lignes. Ne faites jamais disparaître silencieusement les lignes dont la clé est introuvable : écrivez-les dans un fichier séparé pour qu'une personne puisse les vérifier.
Faut-il avoir Excel installé pour créer un fichier Excel (xlsx) avec PowerShell ?
Non. Le module communautaire ImportExcel permet de lire et d'écrire des fichiers xlsx, de créer des tableaux, d'appliquer une mise en forme et même de générer des tableaux croisés dynamiques, sur une machine où Excel n'est pas installé. Il s'installe avec Install-Module depuis PowerShell Gallery. Piloter Excel lui-même via COM doit être considéré comme un dernier recours, réservé aux cas où vous avez réellement besoin d'une fonctionnalité propre à Excel, comme l'exécution d'une macro.
Pourquoi EXCEL.EXE reste-t-il en mémoire quand on pilote Excel via COM depuis PowerShell ?
Parce que si les références aux objets COM (RCW) ne sont pas libérées, le processus Excel ne se termine pas même après avoir appelé Quit. Chaque fois que vous touchez un classeur ou une plage de cellules, une référence à un objet intermédiaire supplémentaire est créée ; une fois le travail terminé, il faut les libérer explicitement avec Marshal.ReleaseComObject et déclencher le ramasse-miettes avec GC.Collect. Par ailleurs, Microsoft ne recommande pas et ne prend pas en charge l'automatisation d'Office dans un environnement sans surveillance comme le Planificateur de tâches ; les traitements destinés à une exécution planifiée devraient donc s'orienter vers des méthodes ne nécessitant pas Excel lui-même, comme ImportExcel.
Pour agréger un CSV, faut-il utiliser un tableau croisé dynamique Excel ou PowerShell ?
Pour une analyse ponctuelle, Excel suffit amplement. Si vous répétez la même procédure chaque semaine ou chaque mois, cela vaut la peine de la scripter avec Group-Object et Measure-Object. La procédure est alors conservée sous forme de code plutôt que de documentation, ce qui permet de reproduire le même résultat même si la personne en charge change, et ouvre la voie à une exécution planifiée via le Planificateur de tâches. En exportant le résultat de l'agrégation en xlsx avec ImportExcel, le destinataire peut le traiter comme un fichier Excel ordinaire.

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