Powershell : rapport d’état d’occupation des disques locaux de serveurs

J’ai développé un script Powershell utilisant WMI pour adresser à tous les ordinateurs d’une sélection d’OU ActiveDirectory une requête pour connaître l’état de leurs disques. Le but de ce script est de pouvoir générer des rapports d’état (au format CSV ou XML) pour prévoir l’occupation de certaines plateformes et tracer une évolution, afin de pouvoir agir en conséquence (libération d’espace disque, augmentation du disque…)

Bien que la requête soit identique pour les deux scripts, le traitement pour un export CSV est différent de celui qui génère un fichier XML.

Le script permettant de réaliser le rapport en CSV :

function dothemaths {
	param($Disk)
	$Size = $Disk.Size
	$FS = $Disk.FreeSpace
	$DiskPerc = [math]::Round(($FS/$Size)*100,0)
	return $DiskPerc
}

$Output = @()
$SearchBases = @('OU=APP','OU=FILERS','OU=PRINT','OU=ADMIN')
foreach($SearchBase in $SearchBases){
	$Comps = Get-AdComputer -filter * -SearchBase "$SearchBase,OU=SERVERS,DC=dundermifflin,DC=inc" | select name
	foreach($Comp in $Comps){
		$DiskList = Get-WmiObject Win32_LogicalDisk -ComputerName $Comp.name
		foreach ($Disk in $DiskList){
			if($Disk.DriveType -ne 3){ continue }
			$Temp = New-Object PSCustomObject
			Add-Member -InputObject $Temp -Type NoteProperty -Name 'ComputerName' -Value $Comp.name
			Add-Member -InputObject $Temp -Type NoteProperty -Name 'DiskLetter' -Value $Disk.DeviceID
			Add-Member -InputObject $Temp -Type NoteProperty -Name 'DiskName' -Value $Disk.VolumeName
			Add-Member -InputObject $Temp -Type NoteProperty -Name 'Size' -Value $Disk.Size
			Add-Member -InputObject $Temp -Type NoteProperty -Name 'FreeSpace' -Value $Disk.FreeSpace
			Add-Member -InputObject $Temp -Type NoteProperty -Name 'FreeSpacePercentage' -Value (dothemaths($Disk))
			$Output+=$Temp
		}
	}
}
$Output | Export-CSV DiskReport.csv

Le format CSV peut ensuite être ouvert et exploité dans Excel. Afin de convertir les valeurs exprimées en octet en Mo ou en Go par exemple, il suffira de réaliser un collage spécial sur les colonnes pour faire une division par le nombre correspondant (1073741824 par exemple pour un affichage en Go).

Le script permettant de réaliser un rapport au format XML :

function dothemaths {
	param($Disk)
	$Size = $Disk.Size
	$FS = $Disk.FreeSpace
	$DiskPerc = [math]::Round(($FS/$Size)*100,0)
	return $DiskPerc
}

$XML = New-Object System.Xml.XmlDocument
$XMLd = $XML.CreateXmlDeclaration("1.0","UTF-8",$null)
$XML.AppendChild($XMLd) | out-null
$XMLn = $XML.CreateNode("element","Servers",$null)
$SearchBases = @('OU=APP','OU=FILERS','OU=PRINT','OU=ADMIN')
foreach($SearchBase in $SearchBases){
	$Comps = Get-AdComputer -filter * -SearchBase "$SearchBase,OU=SERVERS,DC=dundermifflin,DC=inc" | select name
	foreach($Comp in $Comps){
		$DiskList = Get-WmiObject Win32_LogicalDisk -ComputerName $Comp.name
		$CompNode = $XML.CreateNode("element","Server",$null)
		$CompNode.SetAttribute("Name",$Comp.name)
		foreach ($Disk in $DiskList){
			if($Disk.DriveType -ne 3){ continue }			
			$XMLe = $XML.CreateElement("DiskLetter")
			$XMLe.InnerText = $Disk.DeviceID
			$CompNode.AppendChild($XMLe) | out-null
			$XMLe = $XML.CreateElement("DiskName")
			$XMLe.InnerText = $Disk.VolumeName
			$CompNode.AppendChild($XMLe) | out-null
			$XMLe = $XML.CreateElement("Size")
			$XMLe.InnerText = $Disk.Size
			$CompNode.AppendChild($XMLe) | out-null
			$XMLe = $XML.CreateElement("FreeSpace")
			$XMLe.InnerText = $Disk.FreeSpace
			$CompNode.AppendChild($XMLe) | out-null
			$XMLe = $XML.CreateElement("FreeSpacePercentage")
			$XMLe.InnerText = dothemaths($Disk)
			$CompNode.AppendChild($XMLe) | out-null
		}
		$XMLn.AppendChild($CompNode) | out-null
	}
}
$XML.AppendChild($XMLn) | out-null
$XML.Save("DiskReport.xml")

Ensuite, le fichier XML pourra être importé et exploité dans un objet Powershell, comme je l’ai expliqué dans ce tutoriel.

Création et exploitation de fichiers XML avec Powershell

Je vais aborder dans cet article les possibilités qu’offre Powershell sur les fichiers XML. Récemment, j’ai développé un script réalisant un audit de l’ActiveDirectory au format HTML (que je partagerai éventuellement plus tard) et qui exporte certaines données dans un fichier XML, afin qu’elles puissent être exploitées plus facilement, par exemple pour avoir un historique et tracer des courbes d’évolution.

Nous allons donc utiliser les diverses méthodes de la classe Xml de .NET pour créer les différentes entrées, la hiérarchie et les attributs de ce que nous souhaitons stocker. Le script présenté dans cet article va récupérer les membres d’un groupe ActiveDirectory ainsi que des informations concernant ces membres, afin de tout stocker dans un fichier XML. Voici à quoi va ressembler mon fichier à la fin de l’exécution du code :

Tout d’abord, la déclaration de l’objet et l’écriture de la déclaration XML, qui est nécessaire pour que Powershell puisse gérer le fichier. Afin d’éviter d’écrire dans la console, j’ai besoin de piper out-null à chaque appel de AppendChild.

[xml]$xmlfile = New-Object System.Xml.XmlDocument
$xmldecl = $xmlfile.CreateXmlDeclaration("1.0","UTF-8",$null)
$xmlfile.AppendChild($xmldecl) | out-null

On créé le nœud racine <users>.

$xmlroot = $xmlfile.CreateNode("element","users",$null)

Les requêtes ActiveDirectory pour récupérer les membres du groupe en question, et la récupération des informations de ces utilisateurs :

$adGroupM = Get-AdGroupMember -identity "sales"
$adUsers = $adGroupM | foreach { Get-AdUser $_ -Properties DisplayName, mail, TelephoneNumber }

C’est ensuite dans une boucle ForEach que tout le traitement va avoir lieu. En effet, il va être nécessaire de jouer pour chaque utilisateur la création des éléments DisplayName, Mail et TelephoneNumber.

$adUsers | foreach {
	$userNode = $xmlfile.CreateNode("element","user",$null)
	$userNode.SetAttribute("Name",$_.samAccountName)
	$element = $xmlfile.CreateElement("DisplayName")
	$element.InnerText = $_.DisplayName
	$userNode.AppendChild($element) | out-null
	$element = $xmlfile.CreateElement("Mail")
	$element.InnerText = $_.mail
	$userNode.AppendChild($element) | out-null
	$element = $xmlfile.CreateElement("TelephoneNumber")
	$element.InnerText = $_.TelephoneNumber
	$userNode.AppendChild($element) | out-null
	$xmlroot.AppendChild($userNode) | out-null
}

Explications de ce snippet :

  • $UserNode désigne un utilisateur. Au niveau XML, il s’agit donc de <user>, qui contient <DisplayName>, <Mail> et <TelephoneNumber>.
  • Afin de pouvoir afficher le nom de l’utilisateur dans la balise XML <user> de manière à ce qu’il soit écrit <user Name= »mscott »>, il faut utiliser SetAttribute. Il est possible d’ajouter plusieurs attributs.
  • $Element désigne ce que l’on va ajouter à la balise parente. Il s’agit donc ici de <DisplayName>, <Mail> et <TelephoneNumber>. Ces éléments n’ont pas d’enfants, à l’inverse de <user>. On considère que <user> est un nœud (ou node) et <DisplayName> un élément. La fonction InnerText permet de spécifier la valeur : ici il s’agira du DisplayName, de l’adresse mail et du numéro de téléphone, que l’on récupère grâce à la variable temporaire $_ de la boucle ForEach.
  • Pour finir, on rattache l’élément créé au nœud parent correspondant, dans notre cas $UserNode. Lorsque l’élément a été rattaché, on obtient donc un nœud <User> complet. De la même manière, on le rattache au nœud racine du fichier XML, dans notre cas $xmlroot, qui s’appelle <users>.

En reprenant la capture d’écran plus haut, voici comment est découpé le fichier XML. En rouge le nœud racine $xmlroot ; en vert les noeuds $UserNode ; en orange les éléments $Element qui dépendent donc de $UserNode, lui-même dépendant de $xmlroot, dépendant finalement de $xmlfile.

Une fois que tous les nodes ont bien été traités, il faut préparer le fichier final. XML étant un format hiérarchique, $xmlroot n’est ni plus ni moins qu’un nœud de l’objet XML que nous avons déclaré en début de script, $xmlfile.

$xmlfile.AppendChild($xmlroot) | out-null

Pour conclure, l’écriture du fichier sur le disque :

$xmlfile.Save("sales-dundermifflin.xml")

Importons ce fichier XML dans Powershell :

[xml]$sales = Get-Content sales-dundermifflin.xml

Ensuite, appeler $sales n’aura aucun intérêt. Par contre, en utilisant les noms des nœuds et des éléments, on peut donc afficher et manipuler les informations comme on le souhaite :

Et pour reprendre le même code couleur que précédemment :

En rouge, l’ensemble du node Users, en vert les 4 noeuds User et en orange, les éléments composant chaque User, une ligne par node.