Gérer les informations d'identification en toute sécurité sous PowerShell — bannir les mots de passe en clair de vos scripts
· Go Komura · PowerShell, Windows, Sécurité, Informations d'identification, DPAPI, Automatisation, Amélioration opérationnelle, Script
Lors d’enquêtes de dysfonctionnement ou de revues de scripts, quand nous examinons les scripts PowerShell de nos clients, il y a une chose que nous rencontrons avec une probabilité assez élevée : $password = "P@ssw0rd123" — pour se connecter à un serveur de fichiers, à la base de données d’un système central, pour envoyer des e-mails, ou comme clé d’API web. Faute d’avoir priorisé autre chose que « faire fonctionner », le mot de passe reste embarqué en clair dans le script, et survit ainsi pendant des années dans un dossier partagé ou un dépôt Git.
Ce qui est délicat, c’est que la personne qui l’a écrit sait elle-même « que ce n’est pas bien ». Mais quand on cherche la bonne alternative, on tombe sur une prolifération d’options — SecureString, Export-Clixml, SecretManagement, le Gestionnaire d’informations d’identification, Azure Key Vault… — dont le périmètre et les limites respectifs sont difficiles à cerner. Et pour ne rien arranger, on entend même dire que « SecureString n’est plus recommandé », si bien qu’on ne sait plus quoi croire. Cette confusion a une raison d’être, et une fois mise en ordre, la voie à suivre devient claire.
Cet article s’adresse aux responsables informatiques et d’exploitation qui gèrent des scripts de production et des traitements par lots réguliers en interne. Il part de ce qui pose problème avec les mots de passe en clair, met en ordre les outils PowerShell de gestion des informations d’identification mécanisme par mécanisme, et rassemble dans un tableau de décision la réponse concrète à « que faire en exécution sans surveillance ». Il prend PowerShell 7.x comme référence, tout en signalant les précautions à prendre sur le terrain avec Windows PowerShell 5.1.
1. La conclusion, d’abord
- Le problème des mots de passe en clair est qu’ils rendent « la fuite acquise dès que le fichier est vu ». Historique Git, dossier partagé, sauvegarde, journal : toute voie par laquelle une copie du script se multiplie devient une voie de fuite. Écrire du code qui convertit le texte en clair en SecureString (
ConvertTo-SecureString -AsPlainText) ne résout rien si le texte en clair d’origine subsiste dans le script ou dans un journal.12 - Pour un script utilisé de façon interactive, la forme de base consiste à recevoir un PSCredential via
Get-Credential. Le mot de passe ne s’affiche pas à l’écran, et l’objet peut être transmis tel quel au paramètre-Credentialde chaque commande.3 - La documentation officielle .NET précise explicitement qu’elle « ne recommande pas » SecureString pour un nouveau développement. Le chiffrement n’a lieu que sous Windows ; sur les autres plateformes, le contenu interne n’est pas chiffré. PowerShell, de son côté, continue de l’utiliser pour des raisons de compatibilité, et il faudra continuer à composer avec, pour l’instant, comme format d’échange standard. Ne lui accordez pas une confiance excessive et ne vous en servez pas comme base pour un mécanisme de protection maison.45
- Pour stocker des informations d’identification dans un fichier en vue d’une exécution sans surveillance, le chiffrement DPAPI via Export-Clixml est la solution pratique minimale. Comme le déchiffrement n’est possible que par l’utilisateur et la machine qui ont fait l’enregistrement, une fuite isolée du fichier reste inexploitable. Autrement dit, il faut « l’enregistrer avec le compte d’exécution de la tâche lui-même ». Non chiffré sur les plateformes non Windows.6
- Pour gérer plusieurs secrets, centralisez-les avec SecretManagement + SecretStore. Avec l’interface unifiée Set-Secret/Get-Secret, vous pouvez remplacer l’emplacement de stockage sans en changer le code, du SecretStore local jusqu’à Azure Key Vault. La gestion du mot de passe du coffre en exécution sans surveillance reste toutefois un point à traiter.78
- La priorité absolue à envisager est une conception qui ne détient tout simplement pas d’informations d’identification. En accordant les droits sur la destination directement au compte d’exécution (compte de domaine ou gMSA), le mot de passe disparaît du script. Avec un gMSA, la gestion du mot de passe elle-même peut être confiée à l’OS.910
- Intégrez à votre conception le risque de fuite vers les journaux et les transcriptions. La documentation officielle avertit que, si le journal des blocs de script est activé, les données sensibles telles que les informations d’identification utilisées par le script peuvent se retrouver écrites dans le journal des événements. Dès qu’un mot de passe en clair est tapé en ligne de commande, considérez qu’il en reste une trace.11
2. Ce qui pose problème avec le texte en clair — il y a autant de voies de fuite que de copies
Commençons par examiner le schéma typique qu’il faut réécrire.
# Antipatron : les deux versions se valent, car "le texte en clair reste dans le script" dans les deux cas
$password = "P@ssw0rd123"
# Même converti en SecureString, le texte en clair d'origine reste écrit à la première ligne.
# PSScriptAnalyzer détecte cet usage de -AsPlainText comme une erreur
$secure = ConvertTo-SecureString $password -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('svc-transfer', $secure)
Écrire un mot de passe en dur n’est pas dangereux uniquement parce qu’« un intrus malveillant pourrait le lire ». C’est aussi, de par la nature même d’un script en tant que livrable, que les copies se multiplient rien qu’en suivant une exploitation normale.
| Où naît la copie | Ce qui se produit |
|---|---|
| Dépôt Git | Un mot de passe commité une fois reste éternellement dans l’historique. Même en corrigeant le fichier ensuite, il subsiste dans l’historique |
| Dossier partagé | Diffusé à toute personne ayant un droit de lecture, plus les sauvegardes et les copies de génération |
| E-mail / messagerie | Devient incontrôlable dès l’instant où le script est joint avec un « utilise ce script » |
| Journal des événements | Si le journal des blocs de script est activé, le contenu du code exécuté y est enregistré11 |
| Transcription | Start-Transcript ou un paramètre de transcription au niveau de l’organisation enregistre ce qui a défilé à l’écran11 |
Cette structure signifie que la protection contre les mots de passe en clair ne peut pas être obtenue en « empêchant de voir le fichier ». Peu importe à quel point vous restreignez les droits d’accès, cela ne verrouille ni l’historique Git ni les sauvegardes. Sortir le mot de passe du script, vers un emplacement de stockage chiffré, voilà l’approche fondamentale. C’est la même idée que celle exposée à propos des fichiers de configuration d’applications .NET dans « Stocker les secrets des applications Windows - Éviter les configurations en clair avec DPAPI », et le principe ne change pas sous PowerShell.
Notez que convertir du texte en clair en SecureString sur place, avec ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force, n’est pas une solution. PSScriptAnalyzer (l’outil d’analyse statique officiel) détecte cette écriture comme une erreur (Severity: Error). En effet, elle contourne le chiffrement en exposant le texte en clair en mémoire, et le texte en clair d’origine continue de subsister dans le script.1
3. La réalité des outils — PSCredential, SecureString et leurs limites
3.1. Get-Credential et PSCredential
Voici toute la forme de base d’un script interactif.
# Demande le nom d'utilisateur et le mot de passe, reçus sous forme d'objet PSCredential
$cred = Get-Credential -Message "Saisissez le compte de connexion à la base de données du système central"
# Peut être transmis tel quel à une commande disposant d'un paramètre -Credential
Invoke-Command -ComputerName APPSV01 -Credential $cred -ScriptBlock { hostname }
Get-Credential invite à saisir un nom d’utilisateur et un mot de passe, et renvoie un objet PSCredential. Sous Windows PowerShell 5.1, c’est une boîte de dialogue ; à partir de PowerShell 6, la saisie se fait dans la console.3 Le mot de passe est conservé comme SecureString à l’intérieur du PSCredential et ne s’affiche pas à l’écran. Dans les cas où il est acceptable qu’une personne le saisisse sur place, il n’y a rien de plus compliqué à faire.
Du côté de celui qui écrit une fonction personnalisée, concevez-la pour recevoir un paramètre -Credential de type [PSCredential], plutôt qu’un mot de passe en [string]. Le détail de cette écriture est rassemblé dans l’article officiel « Add Credential support to PowerShell functions ».2
3.2. La réalité de SecureString — comment bien comprendre le « non recommandé »
Il y a trois faits à connaître au sujet de SecureString.
- La documentation officielle .NET « recommande de ne pas l’utiliser pour un nouveau développement ». Le chiffrement du tableau interne n’a lieu que sous Windows ; sur les plateformes non Windows, le stockage interne n’est pas chiffré. De plus, comme il faut de toute façon le convertir en une représentation en clair au moment de l’utiliser, son seul effet est de raccourcir la durée d’exposition. L’alternative recommandée est « une référence opaque vers des informations d’identification stockées hors du processus », c’est-à-dire un mécanisme comme le magasin d’informations d’identification de l’OS ou un Key Vault.4
- Malgré cela, l’équipement standard de PowerShell part du principe de SecureString. PowerShell continue de le prendre en charge pour des raisons de compatibilité, et il reste utilisé aujourd’hui pour éviter une exposition accidentelle dans la console ou dans un journal. Il se positionne comme plus sûr qu’une chaîne en clair, sans plus.5
- Il se reconvertit facilement en texte en clair. Sous PowerShell 7,
ConvertFrom-SecureString -AsPlainTextsuffit à lui seul pour obtenir du texte en clair.12 Il est plus fidèle à la réalité de considérer SecureString non pas comme un « coffre-fort illisible », mais tout au plus comme « une enveloppe pour éviter un regard accidentel ».
La conclusion pratique est la suivante : ne vous lancez pas dans un chiffrement maison complexe sous prétexte de SecureString. Utilisez-le comme le format qu’exigent les outils PowerShell (Get-Credential, SecretManagement), et placez le cœur de la protection dans un mécanisme externe au processus, comme DPAPI ou un coffre.
4. Le classique de l’exécution sans surveillance — le stockage DPAPI via Export-Clixml et le mur du « même utilisateur, même machine »
Un script sans surveillance exécuté par le Planificateur de tâches ne peut pas demander à une personne via Get-Credential. La solution pratique minimale est un fichier d’informations d’identification via Export-Clixml.
# --- Préparation (une seule fois, à exécuter avec le compte d'exécution de la tâche) ---
$cred = Get-Credential -Message "Compte d'intégration"
$cred | Export-Clixml -Path 'D:\Jobs\secrets\transfer.credential'
# --- Script de production (exécution sans surveillance) ---
$cred = Import-Clixml -Path 'D:\Jobs\secrets\transfer.credential'
# Exemple : utilisé pour se connecter à un partage nécessitant d'autres informations d'identification, ou pour une exécution à distance
Invoke-Command -ComputerName FILESV01 -Credential $cred -ScriptBlock { Get-ChildItem D:\Export }
Export-Clixml enregistre l’objet d’informations d’identification en le chiffrant avec le DPAPI (Data Protection API) de Windows. Il ne peut être déchiffré que si le compte utilisateur qui l’a enregistré l’ouvre sur ce même ordinateur. Le fichier exporté est inutilisable sur une autre machine ou par un autre utilisateur.613 C’est précisément cette propriété — rester inutilisable même si le fichier est exfiltré — qui le rend pratique comme emplacement de stockage pour l’exécution sans surveillance.
On comprend pourquoi il en va ainsi en jetant un œil, ne serait-ce qu’un niveau, sous le capot. DPAPI chiffre une « clé maîtresse » avec une clé dérivée des informations d’identification de connexion de l’utilisateur (généralement le hachage du mot de passe), et place cette clé maîtresse dans le profil de cet utilisateur. Les données elles-mêmes sont chiffrées avec une clé de session dérivée de cette clé maîtresse. Autrement dit, ce qui détient la clé, c’est le fait même de « pouvoir se connecter en tant que cet utilisateur » : en contrepartie de ne pas avoir à stocker un fichier de clé séparément quelque part, la clé se retrouve liée à l’utilisateur et à la machine. La documentation officielle l’explique aussi ainsi : « en général, seul un utilisateur possédant les mêmes informations d’identification de connexion que celui qui a chiffré peut déchiffrer, et le chiffrement comme le déchiffrement doivent avoir lieu sur le même ordinateur ».14 C’est le contenu de cette limite « même utilisateur, même machine uniquement ».
Cependant, cette limite « même utilisateur, même machine uniquement » est à la fois une protection et un piège opérationnel.
- Il faut faire correspondre le compte utilisé avec le compte d’exécution du Planificateur de tâches. Un fichier .credential créé avec votre propre compte devient indéchiffrable dès l’instant où la tâche s’exécute sous un compte de service. La préparation doit toujours se faire « en tant que compte d’exécution de la tâche » (démarrer PowerShell sous ce compte pour effectuer l’enregistrement). La notion de compte d’exécution de tâche et de type de connexion est détaillée dans « Les tâches du Planificateur de tâches qui ne s’exécutent pas ou se terminent en 0x1 ».
- Un remplacement de serveur ou un changement de compte oblige toujours à refaire l’opération. C’est une cause classique de « ça ne marche plus après la migration » ; scriptez donc la procédure de préparation et conservez-la dans le dépôt (sans, bien sûr, y inclure le mot de passe lui-même).
- Il reste déchiffrable par n’importe quel processus s’exécutant sous ce même compte. DPAPI est ouvert à tout code qui s’exécute en tant que cet utilisateur ; les mesures d’hygiène périphériques restent donc nécessaires : ne pas faire cohabiter de logiciels superflus sur le compte d’exécution, minimiser ses droits.
- Non chiffré sur les plateformes non Windows. La documentation officielle précise explicitement que sous macOS/Linux, la sortie se fait en pratique en clair (un tableau de caractères Unicode).6
Voici la procédure concrète pour « démarrer PowerShell sous ce compte pour effectuer l’enregistrement ». Enregistrez la partie préparation ci-dessus (les deux lignes Get-Credential et Export-Clixml) dans D:\Jobs\save-credential.ps1, puis démarrez PowerShell sous ce compte pour l’exécuter.
# Effectuer la préparation avec le compte d'exécution de la tâche : utiliser runas pour démarrer PowerShell sous ce compte
# Comme runas demande le mot de passe de façon interactive, aucun texte en clair ne subsiste dans la ligne de commande, l'historique ou une transcription
runas /user:CONTOSO\svc-transfer "pwsh.exe -NoProfile -NoExit -File D:\Jobs\save-credential.ps1"
runas utilise des droits équivalents à une connexion interactive ; il échoue donc dans un environnement où ce compte n’est pas autorisé à faire une « connexion locale ». Dans ce cas, il est réaliste d’autoriser la connexion uniquement le temps de la préparation, puis de revenir à la configuration d’origine une fois l’enregistrement terminé. Comme la saisie de Get-Credential nécessite elle-même une session interactive, la méthode consistant à « créer une tâche sans surveillance pour faire la préparation » ne fonctionne pas. Notez aussi que, sur une machine où le profil du compte d’exécution n’a jamais été créé, la première connexion crée d’abord ce profil avant que la clé maîtresse DPAPI ne soit mise en place — c’est aussi l’une des raisons de suivre cette procédure.
Il existe aussi une méthode de chiffrement AES en spécifiant -Key à ConvertFrom-SecureString, mais elle a tendance à simplement déplacer d’un cran le même problème : « comment protéger le fichier de clé ? ».12 Dès que le besoin de franchir plusieurs machines apparaît, il faut passer au coffre du chapitre suivant, ou à la « conception sans détention » du chapitre 6.
5. Quand les secrets se multiplient — SecretManagement et SecretStore
Quand les destinations se multiplient et que des clés d’API ou des jetons s’y mêlent, la dispersion des fichiers .credential atteint ses limites de gestion. C’est là qu’intervient le module SecretManagement. Il s’agit d’une interface unifiée vers un emplacement de stockage des secrets (un coffre), le stockage réel étant assuré par un coffre d’extension. Outre SecretStore (de Microsoft) pour un stockage local, vous pouvez manipuler des coffres d’extension comme Azure Key Vault ou KeePass avec les mêmes commandes.715
# Une seule fois : installation du module et enregistrement du coffre
Install-Module Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore
Register-SecretVault -Name SecretStore -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
# Enregistrement du secret (le mot de passe du coffre est défini au premier accès)
Set-Secret -Name TransferJobCred -Secret (Get-Credential)
# Côté script : récupération par nom. Cette ligne ne change pas même si l'emplacement de stockage change
$cred = Get-Secret -Name TransferJobCred
Outre un PSCredential ou un SecureString, la valeur d’un secret peut aussi être une chaîne, un tableau d’octets ou une table de hachage ; au premier accès, on vous demande de définir un mot de passe protégeant le coffre lui-même.16 L’avantage est que la connaissance de « où c’est stocké » disparaît du script. Passer de SecretStore sur un poste de développement à Azure Key Vault en production (le module Az.KeyVault fournit le coffre d’extension) se fait uniquement en modifiant la configuration de Register-SecretVault.177
En revanche, trois précautions s’imposent pour l’amener en exécution sans surveillance.
- Par défaut, SecretStore exige de façon interactive le mot de passe du coffre. Le déployer tel quel sur le Planificateur de tâches le bloque en attente de l’invite. Pour l’exécution sans surveillance, la documentation officielle recommande de mettre Interaction sur
Noneet de fournir, viaUnlock-SecretStore, le mot de passe du coffre mis à l’abri dans un fichier protégé par DPAPI (Export-Clixml).8 Autrement dit, la clé du coffre finit par être protégée par DPAPI, héritant ainsi de la contrainte du chapitre précédent (même utilisateur, même machine). Il est aussi possible de désactiver purement la demande de mot de passe (Authentication None), mais la clé se retrouve alors protégée uniquement par les droits du système de fichiers, ce qui n’est pas recommandé pour les usages exigeant une forte protection.18 - Ne fonctionne pas avec des comptes gérés comme un gMSA. SecretManagement dépend d’un profil (
$env:LOCALAPPDATA) et de DPAPI, et il est explicitement indiqué qu’il ne prend actuellement pas en charge les comptes gérés Windows dépourvus de profil.15 Si vous souhaitez qu’un job s’exécutant sous un gMSA détienne un secret, cette combinaison n’est pas envisageable (mais avec un gMSA, on peut le plus souvent se rapprocher justement d’une conception sans secret — voir le chapitre suivant). - SecretManagement/SecretStore sont considérés comme « feature complete » (fonctionnalités achevées), et le développement actif de nouvelles fonctionnalités est terminé. Les correctifs de sécurité et les bugs majeurs continuent d’être traités, donc leur usage en soi ne pose pas de problème, mais l’éditeur lui-même indique que « la nature des secrets évolue vers le sans-mot-de-passe ou les informations d’identification fédérées », si bien qu’une conception à long terme devrait aussi envisager de reconsidérer le mode d’authentification lui-même (identité managée Entra ID, etc.).7 Une identité managée est un mécanisme qui obtient un jeton via une identité attribuée à une ressource Azure, et s’authentifie auprès des services compatibles Entra ID sans détenir de mot de passe ni de clé. Pour savoir quoi lire ensuite, commencez par la page de présentation afin de bien saisir la différence entre affectation système et affectation utilisateur,19 et pour l’utiliser depuis PowerShell,
Connect-AzAccount -Identity(connexion via identité managée) du module Az est un point d’entrée sûr.
Il existe aussi une extension communautaire qui utilise le Gestionnaire d’informations d’identification (Credential Manager) de Windows comme coffre.7 Le comportement du Gestionnaire d’informations d’identification lui-même — comme le mécanisme qui utilise automatiquement les informations d’identification enregistrées avec cmdkey — est abordé dans « Les pièges des lecteurs réseau et des chemins UNC », où il apparaît que là aussi, tout se joue au niveau du profil utilisateur.
6. La solution la plus prioritaire — ne pas détenir d’informations d’identification, tout simplement
Jusqu’ici, nous avons empilé les réponses à « comment stocker de façon sûre », mais en réalité, la réponse la plus élégante pour l’exécution sans surveillance ne réside pas dans un artifice de stockage. C’est d’éliminer le besoin, pour le script, de détenir des informations d’identification.
Les tâches et les services Windows s’exécutent dans le contexte de sécurité du compte d’exécution. Si la destination est protégée par une authentification Windows — ACL d’un dossier partagé, authentification Windows d’un serveur SQL, authentification Windows intégrée d’une API interne — il suffit d’accorder les droits sur la destination au compte d’exécution lui-même pour que ni mot de passe ni Get-Secret n’apparaissent jamais dans le script. L’authentification est prise en charge automatiquement par Kerberos, sur la base de l’identité du compte d’exécution.
Ce qui rend cette configuration possible, c’est le gMSA (groupe de comptes de service gérés). Un gMSA est un compte de domaine géré dont la gestion du mot de passe est prise en charge par l’OS Windows.9 Le mot de passe est une génération aléatoire de 240 octets, changée automatiquement par l’OS tous les 30 jours, si bien que personne ne le connaît jamais, et il n’y a pas non plus d’interruption due à son expiration.10 Un gMSA peut être utilisé non seulement pour un service Windows, mais aussi pour une tâche du Planificateur de tâches.20
Voici aussi le squelette de la procédure de mise en place d’un gMSA, car s’arrêter à « c’est utilisable » ne dit pas quoi vérifier ensuite. Comme prérequis, il faut que le niveau fonctionnel du domaine/de la forêt soit Windows Server 2012 ou supérieur, que le compte utilisé pour l’opération soit équivalent à Domain Admins, et que RSAT (module ActiveDirectory) soit installé si le travail se fait ailleurs que sur un contrôleur de domaine.20
# 1) Une seule fois par domaine : créer la clé racine KDS (inutile si elle existe déjà)
# Comme il faut attendre la réplication vers tous les contrôleurs de domaine, cela peut prendre jusqu'à 10 heures avant de pouvoir réellement créer un gMSA
Add-KdsRootKey -EffectiveImmediately
# 2) Créer le gMSA. Dans PrincipalsAllowedToRetrieveManagedPassword,
# indiquer les ordinateurs (regroupés dans un groupe de sécurité) autorisés à récupérer le mot de passe
New-ADServiceAccount -Name svc-transfer -DNSHostName svc-transfer.contoso.local `
-PrincipalsAllowedToRetrieveManagedPassword 'gMSA-Hosts'
# 3) Sur le serveur qui exécutera réellement le job : installer le gMSA et vérifier qu'il peut récupérer le mot de passe
Install-ADServiceAccount -Identity svc-transfer
Test-ADServiceAccount -Identity svc-transfer
La création de la clé racine KDS est une opération à effectuer une seule fois par domaine. Juste après sa création, il n’est pas possible de créer un gMSA, pour des raisons de réplication ; si vous ne voulez pas attendre dans un environnement de test, la documentation officielle propose de spécifier une date passée avec -EffectiveTime ((Get-Date).AddHours(-10)) (ne l’utilisez pas en production).21 Une fois créée, on peut vérifier si l’événement d’ID 4004 est bien enregistré dans le journal opérationnel du service KDS.20
Pour l’affectation à une tâche, indiquez le gMSA comme compte d’exécution de la tâche (un nom de compte se terminant par $, comme CONTOSO\svc-transfer$). Comme personne ne connaît le mot de passe d’un gMSA, la procédure diffère de celle des assistants GUI qui partent du principe de « saisir le mot de passe du compte d’exécution ». En pratique, l’enregistrement se fait avec New-ScheduledTaskPrincipal et Register-ScheduledTask du module ScheduledTasks ; faites de la documentation de ces deux applets de commande votre prochaine étape de recherche. Il peut aussi être nécessaire d’accorder sur ce serveur au gMSA le droit « Ouvrir une session en tant que tâche par lot ».
Voici comment se résume l’ordre de priorité de la décision.
- Si la destination peut être en authentification Windows, faites disparaître les informations d’identification en accordant les droits au compte d’exécution (compte de domaine/gMSA). C’est la première chose à envisager pour un job sans surveillance en environnement de domaine.9
- Ne stockez un secret que pour les destinations où ce n’est pas possible (base en authentification SQL, clé d’API, NAS de groupe de travail, etc.) : un stockage DPAPI via Export-Clixml pour un cas isolé, SecretManagement + SecretStore s’il y en a plusieurs.68
- Pour la connexion à des ressources cloud, privilégiez une identité managée ou des informations d’identification fédérées plutôt que le stockage d’une clé. Même en cas de stockage, dirigez-le vers un coffre dédié comme Azure Key Vault.177
Lorsque vous exécutez un traitement sur un serveur distant en lui transmettant explicitement des informations d’identification, le mécanisme d’authentification de PowerShell Remoting entre aussi en jeu. Consultez « Introduction à PowerShell Remoting (WinRM) ── gérer plusieurs machines Windows en une seule fois », publié en même temps que cet article.
7. Ne pas laisser de secrets dans les journaux et les transcriptions
Même en solidifiant le stockage, cela n’aurait aucun sens si une fuite se produisait via les traces d’exécution. Il y a deux points à retenir.
Premièrement, PowerShell dispose de plusieurs fonctionnalités d’enregistrement de ce qui s’exécute, et il arrive qu’elles soient activées par une configuration au niveau de l’organisation. Si le journal des blocs de script est activé, le contenu de tous les blocs de script traités est enregistré dans le journal des événements. La documentation officielle avertit explicitement que « si l’on active la journalisation des scripts, les informations d’identification et les données sensibles utilisées par le script peuvent se retrouver écrites dans le journal des événements », et recommande d’associer un journal des événements protégé (Protected Event Logging) pour tout usage autre que le diagnostic.11 Le journal des événements protégé est un mécanisme qui chiffre le contenu du journal ciblé avec la clé publique d’un CMS (Cryptographic Message Syntax) au moment de l’écriture, de sorte qu’il ne puisse être déchiffré que dans un autre emplacement sûr détenant la clé privée.11 Il en va de même pour la transcription (la retranscription des opérations) : ce qui défile dans la console reste dans un fichier.
Vous pouvez vérifier l’état de votre propre environnement via les valeurs de registre que la stratégie y écrit. Le journal des événements protégé se configure dans la stratégie de groupe, sous « Configuration ordinateur > Modèles d’administration > Composants Windows > Journalisation des événements (Event Logging) », via « Activer la journalisation des événements protégés (Enable Protected Event Logging) », avec le registre correspondant suivant.22
# Le journal des événements protégé est-il activé (si la stratégie n'est pas configurée, la clé elle-même n'existe pas)
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\EventLog\ProtectedEventLogging' `
-Name EnableProtectedEventLogging -ErrorAction SilentlyContinue
# Stratégie côté journal des blocs de script (branche PowerShell 7)
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\ScriptBlockLogging' `
-Name EnableScriptBlockLogging -ErrorAction SilentlyContinue
# Côté Windows PowerShell 5.1, une clé différente
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' `
-Name EnableScriptBlockLogging -ErrorAction SilentlyContinue
La valeur de registre du journal des blocs de script diffère : elle se trouve sous PowerShellCore pour la branche PowerShell 711, et sous Windows\PowerShell pour Windows PowerShell 5.123. Sur un serveur où 5.1 et 7 cohabitent, vérifiez les deux.
Le point clé à vérifier est la combinaison où seul le journal des blocs de script est activé, tandis que le journal des événements protégé ne l’est pas. C’est un état où « on enregistre, mais on accumule en clair », il faut donc l’identifier avant de déployer un script manipulant des informations d’identification.
Deuxièmement, c’est précisément pour cela qu’il faut s’astreindre à une écriture où « le texte en clair ne transite jamais par la ligne de commande ou par l’écran ».
- Ne concevez pas de recevoir un mot de passe en argument. Dès l’instant où l’on tape
.\job.ps1 -Password "P@ssw0rd", considérez qu’il en reste une trace dans les journaux, l’historique et la liste des processus. Si vous devez le recevoir, faites-le via PSCredential ou SecureString.2
# Convention de réception d'un script personnalisé : ne pas recevoir le mot de passe en [string]
[CmdletBinding()]
param(
# Recevoir les informations d'identification via PSCredential. Si omis, récupération interactive
# via Get-Credential ; en exécution sans surveillance, transmettre le résultat d'Import-Clixml ou de Get-Secret
[Parameter(Mandatory)]
[System.Management.Automation.PSCredential]$Credential
)
# Ne pas sortir en débogage une variable contenant un secret. Se limiter au nom d'utilisateur
Write-Verbose "Compte de connexion : $($Credential.UserName)"
- Ne sortez pas de variable contenant un secret via
Write-HostouWrite-Verboseà des fins de débogage. Une sortie censée être « temporaire » lors d’un dépannage se retrouve pérennisée dans une transcription. - Attention à ce qui se glisse dans les messages d’exception. Si vous construisez une chaîne de connexion avant de lever une erreur, elle a tendance à se retrouver enregistrée en entier dans le journal du bloc catch. Pour la conception de ce qui doit être écrit dans le journal lors du traitement des erreurs, consultez aussi « Gestion des erreurs et conception des nouvelles tentatives sous PowerShell ».
8. Les bonnes pratiques (tableau de décision)
| Situation | Recommandation | Critère de décision |
|---|---|---|
| Exécution sur place par une personne | Get-Credential | Ne rien stocker est le plus sûr. Recevoir via PSCredential et transmettre à -Credential3 |
| Job sans surveillance au sein du domaine (destination en authentification Windows) | Attribution de droits au compte d’exécution/gMSA | Priorité absolue à une conception sans informations d’identification. Un gMSA fonctionne aussi avec le Planificateur de tâches920 |
| Job sans surveillance avec 1 ou 2 secrets | Stockage DPAPI via Export-Clixml | À enregistrer par le compte d’exécution de la tâche lui-même. À refaire en cas de changement de machine/compte6 |
| Job sans surveillance avec de nombreux secrets, emplacement de stockage à remplacer à l’avenir | SecretManagement + SecretStore | Le mot de passe du coffre est fourni via un stockage DPAPI + Unlock-SecretStore. Inutilisable avec un gMSA815 |
| Secret partagé entre ressources cloud/plusieurs serveurs | Azure Key Vault (+ SecretManagement) | Un DPAPI local à la machine ne permet pas le partage. Dès qu’une gestion centralisée et un audit deviennent nécessaires17 |
| Texte en clair dans le script + ConvertTo-SecureString | À réécrire | Une forme peu sûre que PSScriptAnalyzer signale comme erreur. Migrer vers l’une des options ci-dessus1 |
En cas d’hésitation, examinez les options dans cet ordre : « ne pas détenir > détenir en le fixant à une machine (DPAPI) > détenir dans un coffre ».
9. Conclusion
- Le problème fondamental des mots de passe en clair est que toutes les voies de multiplication des copies — historique Git, dossier partagé, journal — deviennent des voies de fuite. La gestion des droits d’accès ne suffit pas à protéger complètement.
- Pour une exécution interactive, Get-Credential + PSCredential suffisent. Ne créez pas de fonction personnalisée recevant un mot de passe en [string].
- SecureString n’est pas recommandé par .NET pour un nouveau développement, mais subsiste comme format d’échange standard de PowerShell. Considérez-le comme « une enveloppe empêchant une exposition accidentelle » et placez le cœur de la protection dans un mécanisme externe.
- Le stockage DPAPI via Export-Clixml est « limité au même utilisateur, à la même machine ». Le point essentiel est de créer le fichier d’enregistrement avec le compte d’exécution du Planificateur de tâches lui-même ; il n’est pas chiffré sur les plateformes non Windows.
- SecretManagement + SecretStore est efficace pour centraliser plusieurs secrets, mais nécessite une conception de fourniture du mot de passe du coffre en exécution sans surveillance, et ne fonctionne pas avec un gMSA. Adoptez-le en gardant à l’esprit qu’il est considéré comme fonctionnellement achevé.
- La priorité absolue est une conception sans informations d’identification. Examinez en premier lieu si vous pouvez faire disparaître le mot de passe du script grâce à l’authentification Windows combinée à l’attribution de droits au compte d’exécution (gMSA).
- Des données sensibles peuvent subsister dans le journal des blocs de script ou les transcriptions. Astreignez-vous à une écriture où le texte en clair ne transite jamais par la ligne de commande, l’écran ou les messages d’exception.
Articles connexes
- Stocker les secrets des applications Windows - Éviter les configurations en clair avec DPAPI
- 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
- Les pièges des lecteurs réseau et des chemins UNC — Travailler avec un serveur de fichiers (dossier partagé) depuis une application métier
- Introduction à PowerShell Remoting (WinRM) ── gérer plusieurs machines Windows en une seule fois
- Gestion des erreurs et conception des nouvelles tentatives sous PowerShell — du piège du try/catch aux bonnes pratiques d’exit code et de retry
- Conception des paramètres et modularisation des scripts PowerShell — Du « script qui fonctionne » au « script que l’on peut transmettre »
Domaines de conseil associés
合同会社小村ソフト (Komura Software LLC) prend en charge l’inventaire des informations d’identification embarquées dans les scripts de production et les traitements par lots réguliers ainsi que leur migration vers un stockage sécurisé, la conception des comptes d’exécution incluant les gMSA, et la revue de sécurité des jobs sans surveillance. N’hésitez pas à nous consulter, ne serait-ce que pour trier des mots de passe en clair laissés de côté avec un « on n’y touche pas parce que ça marche ».
- Conseil technique et revue de conception
- Modification et maintenance de logiciels Windows existants
- Investigation de bugs et analyse des causes
- Contact
Références
-
Microsoft Learn, AvoidUsingConvertToSecureStringWithPlainText. Sur le fait que PSScriptAnalyzer détecte l’utilisation de -AsPlainText avec ConvertTo-SecureString comme une erreur (Severity: Error), qu’elle contourne le chiffrement en exposant des informations sensibles en clair en mémoire, et sur les alternatives citées, Read-Host -AsSecureString ou le module SecretStore. ↩ ↩2 ↩3
-
Microsoft Learn, Add Credential support to PowerShell functions. Sur la façon d’ajouter un paramètre PSCredential à une fonction personnalisée, et sur la raison pour laquelle un avertissement est émis lors de l’utilisation de ConvertTo-SecureString -AsPlainText, un mot de passe en clair étant enregistré dans divers journaux. ↩ ↩2 ↩3
-
Microsoft Learn, Get-Credential. Sur le fait que Get-Credential invite à saisir un nom d’utilisateur et un mot de passe puis renvoie un objet PSCredential, que la saisie se fait via une boîte de dialogue sous Windows PowerShell 5.1 et dans la console à partir de PowerShell 6, et sur son usage en le transmettant à une commande disposant d’un paramètre -Credential. ↩ ↩2 ↩3
-
Microsoft Learn, SecureString Class. Sur la recommandation de ne pas utiliser SecureString pour un nouveau développement sous .NET (Core), sur le fait que le chiffrement n’a lieu que sous Windows et que le stockage interne n’est pas chiffré sur les plateformes non Windows, sur la nécessité de le convertir en représentation en clair au moment de l’utiliser, et sur le fait que l’alternative recommandée est une référence opaque vers des informations d’identification stockées hors du processus. ↩ ↩2
-
Microsoft Learn, Advisory Development Guidelines. Sur le fait que, bien que .NET ne recommande pas la nouvelle utilisation de SecureString, PowerShell continue de le prendre en charge pour la rétrocompatibilité, qu’il est plus sûr qu’une chaîne en clair et sert à éviter une exposition accidentelle dans la console ou les journaux, et qu’il doit être utilisé avec prudence car il se convertit facilement en clair. ↩ ↩2
-
Microsoft Learn, Export-Clixml. Sur le fait qu’Export-Clixml enregistre l’objet d’informations d’identification en le chiffrant avec le DPAPI de Windows, que le déchiffrement n’est possible que sur le compte utilisateur et l’ordinateur qui ont fait l’enregistrement, rendant le fichier exporté inutilisable sur une autre machine ou par un autre utilisateur, et que sur les plateformes non Windows (macOS/Linux) il n’est pas chiffré et sort en pratique en clair. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Overview of the SecretManagement and SecretStore modules. Sur le fait que SecretManagement est une interface unifiée vers des coffres d’extension, que SecretStore est un coffre d’extension multiplateforme qui stocke de façon chiffrée dans un fichier local, qu’il existe des coffres d’extension comme Azure Key Vault, KeePass ou le Gestionnaire d’informations d’identification (CredMan), et que les modules Secret sont considérés comme fonctionnellement achevés, le développement actif étant terminé et seuls les correctifs de sécurité continuant. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Use the SecretStore in automation. Sur la configuration d’Interaction sur None pour SecretStore en vue d’une exécution sans surveillance, sur le fait de stocker le mot de passe du coffre dans un fichier chiffré par DPAPI via Export-Clixml et de le déverrouiller avec Unlock-SecretStore, et sur le fait qu’il s’agit d’une solution limitée à Windows. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Group Managed Service Accounts overview. Sur le fait qu’un gMSA est un compte de domaine géré offrant une gestion automatique du mot de passe et une simplification de la gestion des SPN, la gestion du mot de passe étant prise en charge par l’OS Windows plutôt que par un administrateur. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Secure group managed service accounts. Sur le fait que le mot de passe d’un gMSA est une génération aléatoire de 240 octets, que l’OS le change automatiquement tous les 30 jours ce qui rend inutiles la planification des changements de mot de passe et les interruptions de service, et sur la recommandation d’utiliser un gMSA comme type de compte pour les services on-premises. ↩ ↩2
-
Microsoft Learn, about_Logging_Windows. Sur le fait que, si le journal des blocs de script est activé, le contenu de tous les blocs de script traités par PowerShell est enregistré dans le journal des événements, qu’en augmentant le niveau de journalisation des données sensibles comme les informations d’identification utilisées par le script peuvent y figurer, que la valeur de registre activant le journal des blocs de script pour la branche PowerShell 7 est
EnableScriptBlockLoggingsousHKLM:\Software\Policies\Microsoft\PowerShellCore\ScriptBlockLogging, et que Protected Event Logging est un mécanisme qui chiffre le contenu du journal des événements avec la clé publique d’un CMS (Cryptographic Message Syntax) pour ne le déchiffrer que dans un emplacement sûr détenant la clé privée. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
Microsoft Learn, ConvertFrom-SecureString. Sur la possibilité de convertir un SecureString en chaîne standard chiffrée, sur le fait que le DPAPI de Windows est utilisé si aucune clé n’est spécifiée et qu’AES est utilisé si Key/SecureKey est spécifié, et sur la conversion directe en chaîne en clair via -AsPlainText. ↩ ↩2
-
Microsoft Learn, Import-Clixml. Sur le fait qu’Import-Clixml permet de restaurer les informations d’identification ou les chaînes sécurisées enregistrées par Export-Clixml, ce qui évite le risque d’écrire un mot de passe en clair dans le script. ↩
-
Microsoft Learn, CryptProtectData function. Sur le fait qu’en général, seul un utilisateur possédant les mêmes informations d’identification de connexion que celui qui a chiffré peut déchiffrer, que le chiffrement et le déchiffrement doivent en général avoir lieu sur le même ordinateur, et que la fonction crée une clé de session à partir des informations d’identification de connexion de l’utilisateur pour effectuer le chiffrement. ↩
-
Microsoft Learn, Understanding the SecretManagement module. Sur le fait que SecretManagement est un mécanisme qui stocke et récupère des secrets via un coffre d’extension enregistré, que l’enregistrement du coffre se fait par contexte utilisateur, et que la dépendance à $env:LOCALAPPDATA et à DPAPI fait qu’il ne fonctionne actuellement pas avec les comptes gérés (managed accounts) de Windows. ↩ ↩2 ↩3
-
Microsoft Learn, Get started with the SecretStore module. Sur l’enregistrement de SecretStore via Register-SecretVault, les opérations de base Set-Secret/Get-Secret/Get-SecretInfo, et la demande de définition du mot de passe du coffre au premier accès. ↩
-
Microsoft Learn, Use Azure Key Vault in automation. Sur le fait qu’Az.KeyVault 3.3.0 et ultérieur inclut l’extension SecretManagement, et sur la possibilité d’enregistrer Azure Key Vault comme coffre SecretManagement via Register-SecretVault pour le manipuler avec Get-Secret, etc. ↩ ↩2 ↩3
-
Microsoft Learn, Understanding the security features of SecretManagement and SecretStore. Sur le fait que si l’authentification par mot de passe est totalement désactivée, la clé de déchiffrement n’est plus protégée que par les droits du système de fichiers, ce qui n’est pas recommandé pour les systèmes exigeant une forte protection. ↩
-
Microsoft Learn, What are managed identities for Azure resources?. Sur le fait qu’une identité managée est un mécanisme qui obtient un jeton en utilisant une identité sur Entra ID, sans gérer d’informations d’identification, et sur les deux types — affectée par le système et affectée par l’utilisateur — ainsi que leurs différences. ↩
-
Microsoft Learn, Manage group Managed Service Accounts. Sur le fait qu’un gMSA peut être utilisé pour un service configuré via le gestionnaire de contrôle des services, un pool d’applications IIS ou une tâche du Planificateur de tâches, sur les prérequis (niveau fonctionnel du domaine/de la forêt Windows Server 2012 ou supérieur, appartenance à Domain Admins/Enterprise Admins, présence de la clé racine KDS vérifiable via l’ID d’événement 4004 du journal opérationnel KdsSvc, nécessité de RSAT pour travailler ailleurs que sur un contrôleur de domaine), sur la procédure de création avec New-ADServiceAccount et la spécification de PrincipalsAllowedToRetrieveManagedPassword, et sur la mise en place et la vérification via Install-ADServiceAccount/Test-ADServiceAccount. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Create the Key Distribution Services KDS Root Key. Sur la nécessité de la clé racine KDS pour la génération du mot de passe d’un gMSA, sur sa création via Add-KdsRootKey -EffectiveImmediately et la spécification selon laquelle il faut attendre jusqu’à 10 heures pour la convergence de la réplication vers tous les contrôleurs de domaine, et sur la procédure consistant à spécifier une date passée avec Add-KdsRootKey -EffectiveTime pour éviter ce délai d’attente en environnement de test. ↩
-
Microsoft Learn, ADMX_EventLogging Policy CSP. Sur le fait que la stratégie EnableProtectedEventLogging se trouve dans « Configuration ordinateur > Composants Windows > Journalisation des événements », que la clé de registre correspondante est
Software\Policies\Microsoft\Windows\EventLog\ProtectedEventLogging, et que le nom de la valeur estEnableProtectedEventLogging. ↩ -
Microsoft Learn, about_Logging (Windows PowerShell 5.1). Sur le fait que, sous Windows PowerShell 5.1, la valeur de registre activant le journal des blocs de script est
EnableScriptBlockLoggingsousHKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging. ↩
Articles associés
Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.
Politique d'exécution PowerShell et signature de scripts — Guide pratique pour sortir de l'exploitation « on colmate avec Bypass »
La politique d'exécution de PowerShell est « un dispositif de sécurité, pas une frontière de sécurité ». Cet article présente les différe...
Où regarder quand un script PowerShell est lent — les points clés sur les tableaux, le pipeline et le rapprochement de données
Ce guide passe en revue les causes classiques de lenteur des scripts PowerShell : pourquoi l'opérateur += sur un tableau devient O(n²), l...
Arrêter d'utiliser Write-Host — Conception des flux de sortie et de la journalisation dans PowerShell
Ce guide présente les six flux de sortie de PowerShell et leurs usages respectifs, les problèmes posés par Write-Host et son bon usage, l...
Le traitement parallèle dans PowerShell — Choisir entre ForEach-Object -Parallel et les jobs
Ce guide présente, d'un point de vue pratique, les différences et les usages respectifs de ForEach-Object -Parallel, Start-ThreadJob et S...
Conception des paramètres et modularisation des scripts PowerShell — Du « script qui fonctionne » au « script que l'on peut transmettre »
Ce guide organise les étapes pour élever la qualité d'un script PowerShell jusqu'à pouvoir le transmettre à d'autres personnes. Il couvre...
Sujets associés
Ces pages replacent le sujet dans un contexte plus large de services et de décisions.
Thèmes techniques Windows
Portail des sujets sur le développement Windows, l'analyse des incidents et la valorisation des actifs existants.
Services liés à ce sujet
Cet article est directement lié aux services suivants.
Développement d'applications Windows
Applications métier, intégration d'équipements et outils de communication, des besoins au développement.
Questions fréquentes
Questions souvent posées lors d’une consultation sur le sujet de cet article.
- Pourquoi ne faut-il jamais écrire un mot de passe en clair dans un script PowerShell ?
- Parce qu'un script est un livrable destiné, par nature, à être copié, partagé et conservé dans un historique. Une fois commité dans Git, il est difficile de l'effacer de l'historique ; placé dans un dossier partagé, il devient visible de toute personne ayant un droit de lecture ; et le journal des blocs de script ou une transcription enregistrent le contenu des commandes tel quel. Autrement dit, un mot de passe en clair crée une structure où « la fuite est acquise dès que le fichier est vu ». La bonne approche n'est pas de commencer par fermer les voies de fuite, mais de sortir le mot de passe du script pour le placer dans un emplacement protégé.
- Jusqu'où les informations d'identification enregistrées avec Export-Clixml sont-elles sûres ?
- Sous Windows, elles sont chiffrées avec DPAPI (Data Protection API) et ne peuvent être déchiffrées que par le compte utilisateur qui les a enregistrées, sur ce même ordinateur. Même si le fichier est dérobé, il reste inutilisable sur une autre machine ou par un autre utilisateur, ce qui en fait un choix pratique pour un stockage destiné à l'exécution sans surveillance. Ce n'est cependant pas une solution universelle, car n'importe quel processus s'exécutant sous ce même compte peut le déchiffrer ; notez aussi que sous macOS ou Linux, le contenu n'est pas chiffré et sort pratiquement en clair. Utilisé depuis le Planificateur de tâches, le fichier doit être créé par le compte d'exécution de la tâche lui-même.
- Faut-il encore utiliser SecureString aujourd'hui ?
- La documentation officielle de .NET indique explicitement qu'elle ne recommande pas l'usage de SecureString pour un nouveau développement. Le chiffrement n'a lieu que sous Windows, et le contenu interne n'est pas chiffré sur les plateformes non Windows. De plus, il faut de toute façon le reconvertir en clair au moment de l'utiliser. Cela dit, dans l'univers PowerShell, les outils standards comme Get-Credential ou SecretManagement partent du principe de SecureString, et comme cela réduit l'exposition par rapport à faire circuler une chaîne en clair, il est réaliste, pour l'instant, de continuer à s'en servir comme « format d'échange standard de PowerShell ». Ne vous en servez pas comme brique de base pour construire votre propre mécanisme de chiffrement.
- Utiliser SecretStore dans une exécution sans surveillance du Planificateur de tâches ne va-t-il pas bloquer sur une demande de mot de passe ?
- Avec la configuration par défaut, oui, cela bloque. Par défaut, SecretStore exige un mot de passe de coffre et affiche une invite interactive. Pour l'exécution sans surveillance, la documentation officielle recommande de mettre Interaction sur None et de charger le mot de passe du coffre depuis un fichier protégé par DPAPI (Export-Clixml), déverrouillé avec Unlock-SecretStore. Cela reste toutefois une configuration où « la clé du coffre est protégée par DPAPI », qui hérite donc en définitive des contraintes de DPAPI (même utilisateur, même machine). Avant d'en arriver là, examinez d'abord s'il est possible de rendre les informations d'identification elles-mêmes inutiles, grâce à un gMSA ou à l'attribution de droits au compte d'exécution.
- Existe-t-il un moyen de ne pas stocker d'informations d'identification du tout ?
- Oui, et c'est même le premier choix à envisager. Les tâches et services Windows s'exécutent avec les droits du compte d'exécution ; si vous accordez à ce compte lui-même les droits sur la destination (dossier partagé, authentification Windows d'un serveur SQL, etc.), le script n'a plus jamais besoin de détenir de mot de passe. En faisant du compte d'exécution un gMSA (groupe de comptes de service gérés), le mot de passe devient une valeur aléatoire de 240 octets que l'OS renouvelle automatiquement tous les 30 jours, si bien que personne ne connaît jamais ce mot de passe. Un gMSA peut aussi être utilisé pour une tâche du Planificateur de tâches.
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.