Refonte des scripts de sauvegarde vers un NAS : suppression des mots de passe en clair, stockage des secrets via DPAPI (Export-Clixml), exécution fiable avec Robocopy et déploiement industrialisé des tâches planifiées.
Faille identifiée. Les scripts PowerShell déployés sur les machines industrielles
(balance, contrôle de température, autres équipements de production) contenaient les mots de
passe en clair dans le code source. Tout utilisateur local pouvait ouvrir le
fichier .ps1 et récupérer les identifiants du compte de sauvegarde.
Les postes industriels exécutaient des scripts de copie vers un partage central ; la revue de sécurité a révélé une exposition critique des identifiants. Les machines produisent des données via un logiciel métier ; un script PowerShell les synchronise vers un NAS centralisé avec Robocopy. L'accès au partage était scripté avec des identifiants intégrés au fichier, ce qui violait le principe de moindre privilège et exposait le SI en cas de compromission d'un poste.
# Exemple illustratif de la faille (valeurs fictives)
$User = "DOMAINE\svc_backup"
$PlainPassword = "MotDePasse_Exemple_NePasFaire"
$SecurePass = ConvertTo-SecureString $PlainPassword -AsPlainText -Force
$cred = New-Object PSCredential($User, $SecurePass)
New-PSDrive -Name "Z" -PSProvider FileSystem -Root "\\NAS-ENTREPRISE\backup\[SITE]" -Credential $cred
robocopy "D:\DonneesMetier" "Z:\Sauvegardes" /E
Export-Clixml pour lier les
identifiants au compte Windows et à la machine.Six étapes : de l'audit à l'automatisation du déploiement des tâches planifiées.
Recensement des scripts par emplacement logique (sites / rôles), inventaire des comptes utilisés
et repérage systématique des chaînes sensibles (ConvertTo-SecureString -AsPlainText,
concaténation de mots de passe, fichiers .ps1 modifiables par des profils non
admin).
Création d'un dossier masqué sous le profil utilisateur et export d'un
SecureString chiffré par DPAPI. Le fichier XML n'est pas réutilisable sur une
autre machine ni sous un autre compte Windows.
$credDir = Join-Path $HOME ".backup_creds"
if (-not (Test-Path -LiteralPath $credDir)) {
$null = New-Item -ItemType Directory -Path $credDir -Force
$dirItem = Get-Item -LiteralPath $credDir
$dirItem.Attributes = $dirItem.Attributes -bor [System.IO.FileAttributes]::Hidden
}
$xmlPath = Join-Path $credDir "nas_credential.xml"
Read-Host "Mot de passe du compte de service (fictif)" -AsSecureString |
Export-Clixml -Path $xmlPath
Les scripts de production importent le secret via Import-Clixml et reconstruisent
un PSCredential. Si l'INIT n'a pas été exécutée, un message explicite évite un
échec silencieux.
$credFile = Join-Path $HOME ".backup_creds\nas_credential.xml"
if (-not (Test-Path -LiteralPath $credFile)) {
Write-Error "Fichier d'identifiants introuvable. Exécutez d'abord le script INIT sur ce poste sous le compte d'exécution de la tâche planifiée."
exit 1
}
$secureFromDisk = Import-Clixml -Path $credFile
$userName = "DOMAINE\svc_backup"
$cred = New-Object System.Management.Automation.PSCredential($userName, $secureFromDisk)
Suppression préalable du lecteur logique pour éviter les conflits, création du
PSDrive avec les identifiants chargés, puis copie avec Robocopy. Les codes de
sortie sont qualifiés : une valeur strictement inférieure à 8 est considérée comme succès.
$driveName = "Z"
if (Get-PSDrive -Name $driveName -ErrorAction SilentlyContinue) {
Remove-PSDrive -Name $driveName -Force -ErrorAction SilentlyContinue
}
$nasRoot = "\\NAS-EXEMPLE\partage\sauvegardes\SITE_GENERIQUE"
New-PSDrive -Name $driveName -PSProvider FileSystem -Root $nasRoot -Credential $cred -Scope Script | Out-Null
$src = "D:\DonneesMetier"
$dst = "${driveName}:\"
& robocopy $src $dst /E /FFT /COPY:DAT /Z /R:3 /W:5 /XJ
$rc = $LASTEXITCODE
if ($rc -ge 8) {
throw "Robocopy en échec (code $rc)"
}
# Codes 0–7 : succès ou situation sans erreur bloquante (documentation Microsoft)
Fonction Write-Log avec quatre niveaux (OK, ECHEC, WARN, INFO), horodatage au
format dd/MM/yyyy HH:mm:ss, couleurs en console. Un try/catch non
bloquant tente de copier le fichier de log vers le NAS sans faire échouer la sauvegarde si le
partage est momentanément indisponible.
$script:LogFile = Join-Path $env:ProgramData "BackupMetier\sauvegarde.log"
$logDir = Split-Path $script:LogFile
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
function Write-Log {
param([ValidateSet('OK','ECHEC','WARN','INFO')][string]$Level, [string]$Message)
$ts = Get-Date -Format 'dd/MM/yyyy HH:mm:ss'
$line = "[$ts] [$Level] $Message"
switch ($Level) {
'OK' { Write-Host $line -ForegroundColor Green }
'ECHEC' { Write-Host $line -ForegroundColor Red }
'WARN' { Write-Host $line -ForegroundColor Yellow }
'INFO' { Write-Host $line -ForegroundColor Cyan }
}
Add-Content -Path $script:LogFile -Value $line
}
try {
Copy-Item -Path $script:LogFile -Destination "\\NAS-EXEMPLE\partage\logs\poste_generique.log" -Force
} catch {
Write-Log -Level WARN "Copie du log vers le NAS impossible : $($_.Exception.Message)"
}
Un second script PowerShell enregistre la tâche en local (New-ScheduledTask) avec
déclencheur (quotidien), compte d'exécution et chemin du script principal.
Intérêt : éviter une configuration manuelle poste par poste lors du déploiement
sur de nombreuses machines industrielles, homogénéiser les paramètres et réduire les erreurs
humaines.
$taskName = "Sauvegarde_Metier_PS"
$scriptPath = "C:\Outils\Sauvegarde\Invoke-BackupSecurise.ps1"
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
$trigger = New-ScheduledTaskTrigger -Daily -At "02:15"
$principal = New-ScheduledTaskPrincipal -UserId "DOMAINE\svc_backup" -LogonType Password -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force
Si la tâche planifiée s'exécute sous un compte différent de celui qui a lancé l'INIT, le déchiffrement peut échouer sans message explicite selon le contexte.
Difficulté : échec silencieux ou erreur peu parlante lors de
Import-Clixml.
Solution : procédure documentée — l'INIT doit être exécutée sous le même compte que la tâche planifiée, sur chaque machine concernée.
Compétence : Sécurité & procédures
Robocopy renvoie par exemple 1 lorsque des fichiers ont été copiés avec succès, ce que
PowerShell peut interpréter comme une erreur si l'on teste uniquement $? ou
0.
Difficulté : faux positifs « échec » alors que la copie est correcte.
Solution : qualification explicite — succès si code de sortie strictement inférieur à 8 ; sinon traitement d'erreur.
Compétence : Scripting & fiabilité
Chaque poste industriel nécessite une INIT sous le bon compte ; multiplier les opérations manuelles augmente le risque d'oubli ou de mauvais compte.
Difficulté : répétition de la procédure sur un parc hétérogène.
Solution : script de création de tâche planifiée + checklist ; automatisation du déploiement des paramètres pour limiter les erreurs humaines.
Compétence : Déploiement & E5
SecureString, Export-Clixml et bonnes pratiques Windows.
Résultat. Les mots de passe ne figurent plus dans le code source ; les secrets sont chiffrés par DPAPI et liés au compte d'exécution. Les sauvegardes sont journalisées et le déploiement des tâches planifiées est reproductible grâce au script d'enregistrement.
Export-Clixml et
Import-Clixml pour retirer les secrets du code.Register-ScheduledTask.