Automatisation Powershell
Comment sécuriser et automatiser vos scripts PowerShell en entreprise
Découvrez les meilleures pratiques DevOps et SecOps pour auditer, signer et exécuter vos scripts PowerShell de nettoyage et maintenance de manière sécurisée.
12 octobre 2026
Pourquoi l'exécution non contrôlée de scripts PowerShell est un risque
Dans les environnements Active Directory et Azure AD, l'automatisation via PowerShell est indispensable pour la gestion de parc et le nettoyage des caches système. Cependant, laisser des scripts non signés s'exécuter en tâches planifiées sans contrôle d'intégrité expose votre infrastructure à des élévations de privilèges et à des exécutions malveillantes.
Pour maintenir un niveau de sécurité maximal, l'application d'une politique d'exécution stricte couplée à une analyse automatique des logs d'exécution est essentielle dans toute stratégie SecOps moderne.
1. Configurer une ExecutionPolicy stricte
La première étape consiste à bloquer les scripts non approuvés au niveau des stratégies de groupe (GPO) ou via Microsoft Intune. L'utilisation du paramètre RemoteSigned ou AllSigned permet de garantir que seuls les scripts développés en interne ou signés par une autorité de confiance locale peuvent être lancés :
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force
Les deux valeurs ne se valent pas. RemoteSigned n'exige une signature que pour les scripts marqués comme téléchargés depuis Internet : un script créé sur la machine s'exécute sans signature. AllSigned l'exige pour tous les scripts, y compris les vôtres. C'est la valeur à viser sur les serveurs et les postes d'administration.
Deux précisions évitent les mauvaises surprises :
- La GPO a le dernier mot. Le paramètre « Activer l'exécution des scripts » (Turn on Script Execution), disponible aussi dans le catalogue de paramètres Intune, l'emporte sur le réglage local et sur l'option
-ExecutionPolicypassée en ligne de commande.Get-ExecutionPolicy -Listaffiche la valeur retenue à chaque niveau. - Ce n'est pas une frontière de sécurité. Microsoft le précise dans sa documentation : l'ExecutionPolicy évite les exécutions accidentelles, elle n'arrête pas un utilisateur décidé, qui peut toujours coller le contenu d'un script dans une console. Pour interdire réellement le code non approuvé, il faut App Control for Business (ex-WDAC) ou AppLocker, qui font basculer PowerShell en mode de langage contraint.
2. Signer vos scripts avec un certificat de signature de code
Une politique AllSigned n'a de sens que si vos propres scripts sont signés. Le certificat vient de votre autorité de certification interne (modèle Code Signing d'AD CS) et il est déployé par GPO dans le magasin « Éditeurs approuvés » des postes. Sans cela, chaque exécution demande une confirmation, et une tâche planifiée, qui ne peut pas répondre, échoue.
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
$signature = @{
FilePath = '.\Clear-TempFiles.ps1'
Certificate = $cert
HashAlgorithm = 'SHA256'
TimestampServer = 'http://timestamp.digicert.com'
}
Set-AuthenticodeSignature @signature
(Get-AuthenticodeSignature .\Clear-TempFiles.ps1).Status # Valid
Trois détails comptent :
HashAlgorithm: Windows PowerShell 5.1 signe encore en SHA-1 par défaut.TimestampServer: l'horodatage garde la signature valide après l'expiration du certificat. Sans lui, tous vos scripts cessent de s'exécuter ce jour-là.- La moindre modification du fichier, même un espace, fait passer le statut à
HashMismatchet le script est refusé. C'est le contrôle d'intégrité recherché.
Signez dans votre pipeline plutôt que depuis un poste : la clé privée reste dans un coffre, et seul le code relu sur la branche principale reçoit une signature. Avant cette étape, le dépôt passe par les mêmes contrôles que n'importe quel code : analyse statique avec PSScriptAnalyzer (Invoke-ScriptAnalyzer -Path .\scripts -Recurse) et détection des secrets committés par erreur, l'une des cinq familles d'outils de notre trousse d'audit de sécurité.
3. Automatiser le nettoyage du disque sans casser les outils de développement
Le script de maintenance le plus répandu est aussi le plus dangereux : vider %TEMP% sans discernement. Sur un poste de développeur, ce dossier n'est pas qu'un cache. Claude Code y garde les répertoires de travail de ses sessions (%TEMP%\claude), et une purge en cours de session supprime ses fichiers de travail. Sa configuration et sa mémoire vivent dans %USERPROFILE%\.claude, les extensions de VS Code dans %USERPROFILE%\.vscode, ses données utilisateur dans %APPDATA%\Code.
Un nettoyage sûr repose sur trois règles : ne supprimer que les fichiers anciens, exclure explicitement les dossiers à préserver, et pouvoir simuler avant d'agir.
#Requires -Version 5.1
[CmdletBinding(SupportsShouldProcess)]
param(
[int]$OlderThanDays = 7
)
# Dossiers à ne jamais purger : données de VS Code et de Claude Code.
# Get-Item les ramène à leur forme longue : $env:TEMP est souvent en nom
# court 8.3 (C:\Users\DUPONT~1\...), alors que Get-ChildItem renvoie le
# chemin long. Sans cette étape, la comparaison échoue et le dossier est purgé.
$protected = @(
(Join-Path $env:TEMP 'claude'),
(Join-Path $env:USERPROFILE '.claude'),
(Join-Path $env:USERPROFILE '.vscode'),
(Join-Path $env:APPDATA 'Code')
) | Where-Object { Test-Path -LiteralPath $_ } |
ForEach-Object { (Get-Item -LiteralPath $_ -Force).FullName }
$limit = (Get-Date).AddDays(-$OlderThanDays)
Get-ChildItem -LiteralPath $env:TEMP -File -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object {
$path = $_.FullName
$_.LastWriteTime -lt $limit -and
-not ($protected | Where-Object { $path.StartsWith("$_\", [StringComparison]::OrdinalIgnoreCase) })
} |
Remove-Item -Force -ErrorAction SilentlyContinue
SupportsShouldProcess apporte le paramètre -WhatIf : .\Clear-TempFiles.ps1 -WhatIf liste ce qui serait supprimé sans rien toucher. Les trois derniers dossiers de la liste ne sont pas sous %TEMP% ; ils sont là pour le jour où quelqu'un élargira la cible à AppData ou au profil entier.
Le piège est décrit dans le commentaire. Sur un poste dont le nom d'utilisateur dépasse huit caractères, $env:TEMP vaut souvent C:\Users\DUPONT~1\AppData\Local\Temp. Comparé tel quel aux chemins longs renvoyés par Get-ChildItem, il ne correspond à rien, et le dossier « protégé » est purgé. Nous l'avons constaté en testant ce script sur un dossier factice avant de le publier : c'est la meilleure raison de lancer -WhatIf avant le premier Remove-Item.
Avant de le planifier, appliquez-lui les règles de tout traitement sans surveillance : relançable sans dégât, code de sortie exploitable, journalisation, aucun secret en clair. Nous les détaillons dans notre note sur les scripts d'automatisation fiables.
4. Planifier l'exécution avec le moins de privilèges possible
Nettoyer son propre dossier temporaire ne demande aucun droit d'administration. La tâche s'exécute donc dans le contexte de l'utilisateur connecté, sans élévation, et non sous un compte de service à hauts privilèges :
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument (
'-NoProfile -NonInteractive -ExecutionPolicy AllSigned ' +
'-File "C:\Program Files\Maintenance\Clear-TempFiles.ps1"'
)
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Friday -At 12:30
# S-1-5-32-545 : le groupe Utilisateurs, quelle que soit la langue de Windows.
$principal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-545' -RunLevel Limited
Register-ScheduledTask -TaskName 'Nettoyage TEMP' -Action $action -Trigger $trigger -Principal $principal
Le script, lui, est stocké dans un dossier que seuls les administrateurs peuvent modifier : la signature protège son contenu, les droits NTFS protègent son emplacement. Si une tâche doit vraiment s'exécuter avec des privilèges élevés, préférez un compte de service administré de groupe (gMSA) à un compte dont le mot de passe n'expire jamais.
5. Journaliser et auditer chaque exécution
Signer dit qui a écrit le script ; journaliser dit ce qui s'est réellement exécuté. Activez par GPO la journalisation des blocs de script (Turn on PowerShell Script Block Logging) : chaque bloc exécuté est consigné sous l'identifiant 4104 dans le journal Microsoft-Windows-PowerShell/Operational, y compris le code décodé d'une commande passée en Base64.
Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-PowerShell/Operational'
Id = 4104
} -MaxEvents 20 | Select-Object TimeCreated, Message
Centralisez ce journal dans votre SIEM, puis alertez sur ce qui n'a rien à faire dans une tâche de maintenance : un téléchargement suivi d'une exécution, du code décodé à la volée, un appel à Invoke-Expression sur du contenu distant. Complétez avec l'audit des créations de processus (événement 4688, ligne de commande incluse), qui montre les lancements en -ExecutionPolicy Bypass ou -EncodedCommand. C'est cette analyse automatique des journaux, plus que la politique d'exécution, qui révèle un script détourné.
Ce qu'il faut retenir
AllSigneddéployée par GPO ou Intune, en sachant qu'elle prévient l'accident, pas l'attaque : le contrôle réel passe par App Control ou AppLocker.- Des scripts signés en SHA-256, horodatés, depuis le pipeline, après analyse statique et détection de secrets.
- Un nettoyage qui ne supprime que l'ancien, exclut les dossiers de VS Code et de Claude Code, et se simule avec
-WhatIf. - Des tâches planifiées sans privilège superflu, et des scripts stockés hors de portée des utilisateurs standards.
- La journalisation des blocs de script centralisée et surveillée.
Pour industrialiser la gestion de parc au-delà de la maintenance (import Autopilot, offboarding Entra ID, profils Intune), notre pack de scripts PowerShell prêts à l'emploi figure parmi nos offres.
Prêt à déployer automatisation powershell chez vous ?
Discutons de votre cas d'usage et voyons comment le déployer en quelques jours, pas en trimestres.