Auditer le stockage virtuel d’un cluster ne consiste pas seulement à additionner les capacités provisionnées des machines. Entre les VMDK thin, les snapshots, les disques détachés après une suppression incomplète ou les fichiers déposés manuellement dans un datastore, l’écart entre capacité logique, consommation physique et espace réellement exploitable peut devenir significatif. PowerCLI permet de produire un inventaire fiable, exportable et réutilisable dans un processus de reporting.
L’objectif de cet article est de fournir un script complet compatible avec VMware.PowerCLI 13 et versions ultérieures. Il audite les VMDK associés aux VM d’un cluster, calcule les métriques de capacité et d’usage, détecte les VMDK orphelins dans les datastores accessibles au cluster, puis exporte les résultats en CSV.
Le script distingue volontairement plusieurs notions :
- Capacité allouée : taille logique du disque vue par le système invité, par exemple 100 Go.
- Espace consommé : espace occupé dans le datastore par le disque et ses fichiers associés, notamment pertinent pour les VMDK thin.
- Provisionné : capacité configurée, indépendamment de l’allocation physique réelle.
- Disque orphelin : fichier
.vmdkdétecté dans un datastore mais non référencé par la configuration d’une VM inventoriée. - Disque attaché : VMDK référencé par une VM, y compris un disque supplémentaire non système.
Préparer l’environnement PowerCLI
Le poste d’administration doit disposer du module VMware.PowerCLI. Les versions récentes s’installent depuis PowerShell Gallery et fonctionnent avec Windows PowerShell 5.1 ou PowerShell 7 selon les composants utilisés.
Install-Module -Name VMware.PowerCLI -Scope CurrentUser
Dans un environnement disposant d’une autorité de certification interne ou d’un vCenter utilisant un certificat non approuvé par le poste d’administration, il peut être nécessaire de désactiver la validation stricte du certificat. Cette option est pratique pour un poste d’exploitation, mais elle ne doit pas masquer un problème de sécurité durable.
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
Le compte utilisé pour la connexion doit au minimum pouvoir lire :
- l’inventaire vCenter ;
- la configuration des VM ;
- les informations de disque virtuel ;
- la navigation dans les datastores ;
- les propriétés de consommation de stockage.
Pour l’inventaire des fichiers orphelins, les droits de navigation datastore sont indispensables. Un compte limité aux objets VM peut produire un rapport partiel ou indiquer à tort que certains fichiers sont absents.
Comprendre les métriques de stockage vSphere
La propriété CapacityGB d’un objet disque PowerCLI correspond à la taille logique du VMDK. Elle est facilement exploitable, mais elle ne suffit pas à mesurer le coût stockage réel. Pour aller plus loin, consultez notre guide complet sur PowerCLI.
Pour les VMDK attachés, PowerCLI expose généralement les propriétés suivantes :
CapacityGB: taille nominale du disque ;StorageFormat: format thin, thick lazy zeroed, thick eager zeroed ou inconnu ;Filename: chemin du descripteur VMDK ;ExtensionData.Backing.FileName: chemin de backing vu par l’API vSphere.
La consommation physique d’un VMDK peut être moins intuitive. Dans un datastore VMFS ou NFS traditionnel, un disque thin ne réserve initialement qu’une faible portion de son espace logique. Sa consommation augmente au fil des écritures de la VM. Sur vSAN, la consommation dépend aussi des objets, de la politique de stockage, de la tolérance aux pannes, de la compression, de la déduplication et des overheads internes.
Le script utilise donc deux sources :
- Les objets
Get-HardDiskpour les VMDK attachés et leur capacité logique. - La navigation datastore avec le provider
VimDatastore:pour relever les fichiers.vmdkréellement présents.
Cette approche permet de comparer les VMDK configurés dans les VM avec les fichiers trouvés dans les datastores du cluster.
Périmètre et limites de la détection des VMDK orphelins
La notion de disque orphelin mérite quelques précautions. Un fichier .vmdk non attaché à une VM n’est pas automatiquement supprimable.
Un VMDK peut être légitime sans être directement attaché à une machine active :
- disque utilisé par une VM hors du cluster audité ;
- disque associé à une VM retirée temporairement de l’inventaire ;
- copie de sauvegarde ou export manuel ;
- disque de template, d’appliance ou de laboratoire ;
- fichier restant après une consolidation de snapshot incomplète ;
- fichier de type delta ou redo lié à une chaîne de snapshots ;
- disque référencé indirectement par une VM dont l’accès au datastore n’est pas visible avec les permissions du compte.
Le rapport doit donc être utilisé comme un inventaire de contrôle, pas comme une liste de suppression automatique. Toute suppression doit être précédée d’une validation par le propriétaire applicatif, d’une vérification de la chaîne VMDK et idéalement d’une sauvegarde ou d’un snapshot de l’infrastructure de gestion lorsque cela est pertinent.
Le script classe les fichiers dans plusieurs états :
Attached: VMDK référencé par une VM du cluster ;OrphanCandidate: fichier VMDK non référencé par les VM du cluster ;SnapshotOrDelta: fichier qui ressemble à un delta de snapshot ou à un fichier auxiliaire, à investiguer ;Unknown: état non déterminé avec suffisamment de certitude.
Script PowerCLI complet
Le script suivant accepte le nom d’un cluster, se connecte à vCenter, collecte les disques attachés, explore les datastores accessibles au cluster et génère trois CSV :
- un export détaillé de tous les VMDK ;
- une synthèse par datastore ;
- une liste dédiée aux candidats orphelins.
Les chemins de sortie sont créés automatiquement si nécessaire.
<#
.SYNOPSIS
Audite les VMDK d'un cluster vSphere et exporte les résultats en CSV.
.DESCRIPTION
Le script collecte :
- Les disques VMDK attachés aux VM du cluster ;
- Leur capacité logique ;
- Leur format de provisioning ;
- Les fichiers VMDK présents dans les datastores accessibles au cluster ;
- Les candidats VMDK orphelins non référencés par les VM auditées.
Attention :
Un VMDK non référencé dans le périmètre du cluster n'est pas forcément
supprimable. Il peut appartenir à une VM hors cluster, être une copie,
ou participer à une chaîne de snapshots.
.PARAMETER VCenter
Nom DNS ou adresse du serveur vCenter.
.PARAMETER ClusterName
Nom exact du cluster vSphere à auditer.
.PARAMETER OutputPath
Répertoire de sortie des fichiers CSV.
.EXAMPLE
.\Audit-ClusterVmdk.ps1 `
-VCenter "vcsa.lab.local" `
-ClusterName "Cluster-Production" `
-OutputPath "C:\Reports\vSphere"
<figure>
<img src="/images/body/body1-script-powercli-inventaire-vmdk-2026.webp" alt="Un inventaire automatisé révèle rapidement les disques orphelins." width="800" height="533" loading="lazy" />
<figcaption>Un inventaire automatisé révèle rapidement les disques orphelins.</figcaption>
</figure>
.NOTES
Compatible VMware.PowerCLI 13+.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$VCenter,
[Parameter(Mandatory = $true)]
[string]$ClusterName,
[Parameter(Mandatory = $true)]
[string]$OutputPath
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# Crée le répertoire d'export si nécessaire.
if (-not (Test-Path -Path $OutputPath)) {
New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
}
# Horodatage unique afin de conserver l'historique des exécutions.
$Timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$DetailedCsv = Join-Path $OutputPath "VMDK-Audit-$ClusterName-$Timestamp.csv"
$OrphanCsv = Join-Path $OutputPath "VMDK-Orphans-$ClusterName-$Timestamp.csv"
$SummaryCsv = Join-Path $OutputPath "VMDK-Summary-$ClusterName-$Timestamp.csv"
# Empêche PowerCLI d'afficher un avertissement CEIP interactif lors d'une première utilisation.
Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -Confirm:$false | Out-Null
try {
Write-Host "Connexion a vCenter : $VCenter" -ForegroundColor Cyan
$VIServer = Connect-VIServer -Server $VCenter
Write-Host "Recherche du cluster : $ClusterName" -ForegroundColor Cyan
$Cluster = Get-Cluster -Name $ClusterName
# Récupère les VM du cluster, y compris les VM éteintes.
# Les templates sont exclus : leurs VMDK peuvent être analysés séparément si nécessaire.
$VMs = Get-VM -Location $Cluster | Where-Object {
-not $_.ExtensionData.Config.Template
}
if (-not $VMs) {
throw "Aucune VM non-template n'a ete trouvee dans le cluster '$ClusterName'."
}
# Datastores accessibles au cluster.
# Cette liste représente le périmètre de recherche des fichiers VMDK.
$Datastores = Get-Datastore -RelatedObject $Cluster | Where-Object {
$_.State -eq "Available"
}
if (-not $Datastores) {
throw "Aucun datastore disponible n'a ete trouve pour le cluster '$ClusterName'."
}
Write-Host "VM trouvees : $($VMs.Count)" -ForegroundColor Green
Write-Host "Datastores disponibles : $($Datastores.Count)" -ForegroundColor Green
# Hashtable des fichiers VMDK réellement référencés par les VM du cluster.
# La clé est normalisée en minuscules pour éviter les écarts de casse.
$AttachedVmdkPaths = @{}
# Liste détaillée des résultats.
$Results = New-Object System.Collections.Generic.List[object]
Write-Host "Collecte des VMDK attaches aux VM..." -ForegroundColor Cyan
foreach ($VM in $VMs) {
$HardDisks = Get-HardDisk -VM $VM
foreach ($HardDisk in $HardDisks) {
# Filename ressemble généralement à :
# [Datastore01] VM-APP01/VM-APP01_1.vmdk
$VmdkPath = $HardDisk.Filename
if ([string]::IsNullOrWhiteSpace($VmdkPath)) {
continue
}
$NormalizedPath = $VmdkPath.ToLowerInvariant()
$AttachedVmdkPaths[$NormalizedPath] = $true
# Estimation de l'espace utilisé :
# CapacityGB reste la taille logique.
# La propriété ExtensionData.Backing est consultée pour enrichir
# le rapport, mais le calcul physique précis dépend du type de datastore.
$CapacityGB = [math]::Round([double]$HardDisk.CapacityGB, 2)
$Results.Add([PSCustomObject]@{
Cluster = $Cluster.Name
Datastore = $HardDisk.Filename -replace '^\[([^\]]+)\].*$', '$1'
VM = $VM.Name
PowerState = $VM.PowerState
DiskName = $HardDisk.Name
VmdkPath = $VmdkPath
CapacityGB = $CapacityGB
UsedSpaceGB = $null
StorageFormat = $HardDisk.StorageFormat
DiskType = $HardDisk.DiskType
Persistence = $HardDisk.Persistence
Status = "Attached"
Notes = "VMDK reference par une VM du cluster"
})
}
}
# Monte le provider PowerCLI pour parcourir les datastores comme un système de fichiers.
# Le provider est créé automatiquement par PowerCLI après connexion a vCenter.
Write-Host "Exploration des fichiers VMDK dans les datastores..." -ForegroundColor Cyan
foreach ($Datastore in $Datastores) {
Write-Host " Analyse datastore : $($Datastore.Name)" -ForegroundColor DarkCyan
$DatastoreRoot = "vmstore:\$($VIServer.Name)\$($Datastore.Name)"
try {
# Recherche récursive des descripteurs .vmdk.
# Les fichiers -flat.vmdk, -delta.vmdk et -sesparse.vmdk sont
# volontairement exclus ici : on inventorie d'abord les descripteurs.
$VmdkFiles = Get-ChildItem -Path $DatastoreRoot -Recurse -ErrorAction Stop |
Where-Object {
-not $_.PSIsContainer -and
$_.Name -match '\.vmdk$' -and
$_.Name -notmatch '(-flat|-delta|-sesparse)\.vmdk$'
} Pour aller plus loin, consultez <a href="/stockage-virtualise/">les fondamentaux du stockage virtualisé</a>.
foreach ($File in $VmdkFiles) {
# Conversion du chemin VimDatastore: vers le format attendu
# par HardDisk.Filename : [Datastore] dossier/fichier.vmdk
#
# Exemple File.DatastoreFullPath :
# [Datastore01] VM-APP01/VM-APP01.vmdk
$DatastoreFullPath = $File.DatastoreFullPath
if ([string]::IsNullOrWhiteSpace($DatastoreFullPath)) {
continue
}
$NormalizedPath = $DatastoreFullPath.ToLowerInvariant()
if ($AttachedVmdkPaths.ContainsKey($NormalizedPath)) {
# Le disque est déjà ajouté dans les résultats attachés.
# On ne crée pas de doublon.
continue
}
# Les suffixes suivants sont fréquents dans les chaînes de snapshots
# ou fichiers auxiliaires. Ils exigent une analyse manuelle.
$IsSnapshotLike = $File.Name -match '-\d{6}\.vmdk$' -or
$File.Name -match 'delta|sesparse|redo'
$Status = if ($IsSnapshotLike) {
"SnapshotOrDelta"
}
else {
"OrphanCandidate"
}
$Notes = if ($IsSnapshotLike) {
"Fichier potentiellement lie a un snapshot ou a une chaine delta : verifier avant toute action"
}
else {
"Descripteur VMDK non reference par les VM du cluster audite"
}
# File.Length correspond a la taille du descripteur, et non au disque virtuel.
# La consommation réelle doit être obtenue via les fichiers associés
# ou les statistiques du datastore. Elle est donc laissée a null ici.
$Results.Add([PSCustomObject]@{
Cluster = $Cluster.Name
Datastore = $Datastore.Name
VM = $null
PowerState = $null
DiskName = $File.Name
VmdkPath = $DatastoreFullPath
CapacityGB = $null
UsedSpaceGB = $null
StorageFormat = $null
DiskType = $null
Persistence = $null
Status = $Status
Notes = $Notes
})
}
}
catch {
Write-Warning "Impossible d'explorer le datastore '$($Datastore.Name)' : $($_.Exception.Message)"
$Results.Add([PSCustomObject]@{
Cluster = $Cluster.Name
Datastore = $Datastore.Name
VM = $null
PowerState = $null
DiskName = $null
VmdkPath = $null
CapacityGB = $null
UsedSpaceGB = $null
StorageFormat = $null
DiskType = $null
Persistence = $null
Status = "DatastoreScanFailed"
Notes = $_.Exception.Message
})
}
}
# Export détaillé : tous les VMDK et les erreurs éventuelles de scan.
$Results |
Sort-Object Datastore, VM, VmdkPath |
Export-Csv -Path $DetailedCsv -NoTypeInformation -Encoding UTF8
# Export dédié aux objets nécessitant une investigation.
$Orphans = $Results | Where-Object {
$_.Status -in @("OrphanCandidate", "SnapshotOrDelta")
}
$Orphans |
Sort-Object Datastore, VmdkPath |
Export-Csv -Path $OrphanCsv -NoTypeInformation -Encoding UTF8
# Synthèse par datastore pour le reporting.
$Summary = $Results |
Group-Object Datastore |
ForEach-Object {
$Attached = $_.Group | Where-Object { $_.Status -eq "Attached" }
$OrphanCandidates = $_.Group | Where-Object { $_.Status -eq "OrphanCandidate" }
$SnapshotCandidates = $_.Group | Where-Object { $_.Status -eq "SnapshotOrDelta" }
[PSCustomObject]@{
Cluster = $Cluster.Name
Datastore = $_.Name
AttachedVmdkCount = $Attached.Count
AttachedProvisionedGB = [math]::Round(
[double](($Attached | Measure-Object -Property CapacityGB -Sum).Sum), 2
)
OrphanCandidateCount = $OrphanCandidates.Count
SnapshotOrDeltaCount = $SnapshotCandidates.Count
DatastoreScanFailed = (@($_.Group | Where-Object {
$_.Status -eq "DatastoreScanFailed"
})).Count
}
}
$Summary |
Sort-Object Datastore |
Export-Csv -Path $SummaryCsv -NoTypeInformation -Encoding UTF8
Write-Host ""
Write-Host "Audit termine." -ForegroundColor Green
Write-Host "Rapport detaille : $DetailedCsv" -ForegroundColor Green
Write-Host "Candidats orphelins : $OrphanCsv" -ForegroundColor Yellow
Write-Host "Synthese : $SummaryCsv" -ForegroundColor Green
Write-Host ""
Write-Host "Resume :" -ForegroundColor Cyan
Write-Host " VMDK attaches : $(@($Results | Where-Object { $_.Status -eq 'Attached' }).Count)"
Write-Host " Candidats orphelins : $(@($Results | Where-Object { $_.Status -eq 'OrphanCandidate' }).Count)"
Write-Host " Snapshots/deltas a verifier : $(@($Results | Where-Object { $_.Status -eq 'SnapshotOrDelta' }).Count)"
}
finally {
if ($VIServer) {
Disconnect-VIServer -Server $VIServer -Confirm:$false
}
}
Exécuter l’audit
Enregistrez le script sous le nom Audit-ClusterVmdk.ps1, puis exécutez-le depuis une session PowerShell disposant de PowerCLI.
.\Audit-ClusterVmdk.ps1 `
-VCenter "vcsa.exemple.local" `
-ClusterName "Cluster-Production" `
-OutputPath "C:\Rapports\vSphere"
Si l’exécution de scripts est bloquée sur le poste d’administration, ne modifiez pas la politique globale sans raison. Une exécution ponctuelle dans le processus courant est souvent suffisante.
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
Le CSV détaillé peut ensuite être ouvert dans Excel, importé dans Power BI ou traité par un script de consolidation. Pour les environnements francophones où Excel attend souvent un séparateur ;, il est possible de remplacer Export-Csv par Export-Csv -Delimiter ';'. Pour aller plus loin, consultez l’administration d’un hôte ESXi.
Interpréter les résultats et compléter le calcul d’usage
Le rapport produit une valeur CapacityGB fiable pour les disques attachés, car elle est issue de la configuration VM. La colonne UsedSpaceGB est volontairement vide dans cette version générique du script. Cette précaution évite de présenter comme exacte une valeur qui varierait selon le type de datastore et le mode de provisioning. Pour aller plus loin, consultez l’automatisation avec Ansible et Terraform.
Sur VMFS et NFS, il est possible d’approcher l’occupation en additionnant les fichiers liés au VMDK : descripteur, fichier -flat.vmdk, fichiers -delta.vmdk et fichiers -sesparse.vmdk. Cette opération doit toutefois être faite avec prudence, car une même chaîne de snapshot peut contenir plusieurs fichiers et les conventions de nommage ne couvrent pas tous les cas.
Sur vSAN, la taille visible dans l’arborescence datastore ne représente pas toujours l’usage physique réel sur les disques du cluster. La politique de stockage, les composants objets, la résilience, l’efficacité des données et les opérations de resynchronisation modifient la consommation. Pour un reporting capacité vSAN, il est préférable de compléter ce script par les vues de capacité vSAN et les métriques d’objets exposées par vCenter.
Une approche pragmatique consiste à utiliser ce script pour répondre à trois questions distinctes :
- Quelle capacité logique est présentée aux VM ?
- Quels VMDK sont encore configurés et attachés ?
- Quels descripteurs VMDK apparaissent dans les datastores sans correspondance dans le périmètre VM audité ?
Cette séparation évite de confondre surallocation, usage physique et déchets de stockage.
Vérifier un candidat orphelin avant suppression
Avant de supprimer un candidat, contrôlez au minimum les éléments suivants :
- le chemin complet du fichier dans le CSV ;
- l’existence d’une VM hors cluster pouvant utiliser le datastore ;
- les fichiers
.vmxvoisins dans le même répertoire ; - la présence de fichiers de snapshot ;
- les tâches récentes de migration, de clonage, de restauration ou de consolidation ;
- les journaux de sauvegarde ;
- le propriétaire applicatif et la fenêtre de changement.
Une vérification rapide peut être réalisée en recherchant le nom du disque dans toutes les VM visibles du vCenter, et non seulement dans le cluster initial.
$SearchName = "serveur-app_1.vmdk"
Get-VM | Get-HardDisk |
Where-Object {
$_.Filename -like "*$SearchName"
} |
Select-Object Parent, Name, Filename, CapacityGB
Pour un chemin précis, recherchez plutôt la valeur complète afin d’éviter les faux positifs sur des noms de fichiers identiques.
$SearchPath = "[Datastore01] VM-APP01/VM-APP01_1.vmdk"
Get-VM | Get-HardDisk |
Where-Object {
$_.Filename -eq $SearchPath
} |
Select-Object Parent, Name, Filename, CapacityGB
Si le disque ne retourne aucun résultat, cela augmente le niveau de confiance, mais ne remplace pas l’analyse de son dossier et de sa chaîne de dépendances. Une suppression directe depuis le datastore browser ou par Remove-Item doit rester une opération exceptionnelle et tracée.
Industrialiser le reporting
Pour transformer cet audit en contrôle récurrent, planifiez son exécution depuis un serveur d’administration ou une automatisation centralisée. Une fréquence hebdomadaire est généralement suffisante pour détecter les dérives de capacité et les VMDK résiduels. Dans les environnements avec de nombreuses opérations de restauration ou de migration, une fréquence quotidienne peut être justifiée.
Conservez les exports dans un répertoire horodaté et comparez les rapports successifs. Les indicateurs utiles sont notamment :
- évolution de la capacité provisionnée par datastore ;
- nombre de candidats orphelins ;
- apparition de fichiers de snapshot inattendus ;
- échecs de scan dus à des droits insuffisants ou à un datastore indisponible ;
- croissance rapide du nombre de VMDK attachés par cluster.
Le rapport est particulièrement efficace lorsqu’il est croisé avec les alertes de capacité datastore, les politiques de durée des snapshots et les comptes rendus des opérations de sauvegarde. L’objectif n’est pas seulement de récupérer de l’espace : il s’agit surtout d’établir une vue cohérente entre l’inventaire vCenter et la réalité des fichiers présents sur les stockages.
