Exchange et Powershell : nettoyage des références caduques dans les ACL

Les ACL des boîtes aux lettres Exchange ne sont pas en synchronisation permanente avec l'ActiveDirectory, ce qui signifie que si l'on ajoute par exemple sur la boîte scranton@dundermifflin.inc l'utilisateur DUNDERMIFFLIN\jhalpert et que ce compte vient à disparaître de l'ActiveDirectory, alors cette boîte aura dans ses ACL une référence de type "S-1-5-***". Il se produit par exemple la même chose côté Windows si un compte de domaine est ajouté dans les profils locaux du système et que ce compte de domaine vient à disparaître.

Afin de faire un peu de nettoyage sur des boîtes aux lettres partagées, j'ai donc développé un script pour l'Exchange Management Shell (EMS) permettant de lister toutes les références à ces comptes qui n'existent plus. Cette liste est ensuite exportée au format CSV, ce qui permet ensuite de passer ce fichier si besoin dans un script supprimant ces références.

$Output = @()
$mailboxes = Get-Mailbox -ResultSize Unlimited
foreach($mailbox in $mailboxes){
	$acls = Get-MailboxPermission $mailbox | where-object { $_.User -like "S-1-5-21*" -and $_.IsInherited -eq $false -and $_.AccessRights -contains "FullAccess" }
	if($acls -ne $null){
		foreach ($acl in $acls) { 
			$obj = New-Object PSCustomObject
			$obj | Add-Member -MemberType NoteProperty -Name "Mailbox" -Value $mailbox.Name
			$obj | Add-Member -MemberType NoteProperty -Name "SID" -Value $acl.User
			$Output += $obj 
		}
	}
}
$Output | Export-CSV s1521-acl.csv -Encoding UTF8

Dans cet exemple, je ne touche qu'aux permissions qui ont été rajoutées manuellement (IsInherited -eq $false) et aux droits complets (FullAccess). Si je ne recommande pas de changer le premier critère, il est possible de changer FullAccess pour un autre valeur si besoin. Je vous renvoie vers la documentation Microsoft concernant la commande Get-MailboxPermission pour plus d'informations sur le sujet.

Une fois le fichier CSV obtenu, je peux me servir de ce snippet pour le parcourir et procéder à la suppression des entrées relevées :

$acls = Import-CSV s1521-acl.csv
foreach($acl in $acls){
	Write-Host "Processing"$acl.Mailbox"with SID"$acl.SID
	Remove-MailboxPermission -Identity $acl.Mailbox -User $acl.SID -AccessRights FullAccess -Confirm:$false
}

Ce premier script est disponible dans une version commentée à télécharger.

Powershell : extraction des adresses du champ "To" d'une entête SMTP

J'ai développé en Powershell un script permettant d'extraire les adresses mail d'une en-tête SMTP. Mon but était de récupérer dans un fichier CSV une liste d'adresses pour effectuer sur ces adresses un traitement supplémentaire.

Dans Outlook, il suffit donc d'ouvrir l'entête SMTP du mail en question, de copier-coller le champ "To" dans un fichier texte et de le passer en paramètre lors de l'exécution du script pour obtenir la liste des adresses dans l'output console Powershell ainsi que dans le fichier CSV passé en paramètre. On peut donc l'appeler ainsi :

.\regex-to-smtp-header.ps1 -Header to.txt -CsvOutput addresses.csv

Voici le script, la version commentée est disponible après celui-ci.

param([Parameter(Mandatory=$true)][string]$Header, [Parameter(Mandatory=$true)][string]$CsvOutput)
$CsvExport = @()
$smtpheader = Get-Content -Path $Header -Raw -ErrorAction stop
while($smtpheader.IndexOf("<") -ne -1) {
    $ls = $smtpheader.IndexOf("<")
    $rs = $smtpheader.IndexOf(">")
    $adrlgt = $rs-$ls
    $adr = $smtpheader.SubString(($ls+1),($adrlgt-1))
    echo $adr
    $adrcsv=@{Address=$adr}
    $adrexp = New-Object PSObject -Property $adrcsv
    $CsvExport+=$adrexp
    $smtpheader = $smtpheader.Substring($rs+1,(($smtpheader.Length-$rs)-1))
}
$CsvExport | Export-CSV $CsvOutput -NoTypeInformation

Powershell : repadmin sur tous les DC du domaine

Je vais partager aujourd'hui un snippet qui va me servir de base pour implémenter un contrôle des réplications qui fonctionne aujourd'hui uniquement sur base d'un appel de dcdiag.

Ce snippet récupère donc les contrôleurs de domaine et récupère les timestamp des dernières réplications. Ensuite, pour chaque DC, il affiche en vert celles qui date de moins d'une heure et en rouge celles qui datent de plus d'une heure.

$dc = (Get-AdDomain).ReplicaDirectoryServers
$csvpath = "C:\temp\replications.csv"
$dc | Foreach { 
	if(Test-Path $csvpath) { Remove-Item $csvpath }
	Add-Content -Path $csvpath -Value (repadmin /showrepl $_ -csv)
	$csvcontent = Import-CSV $csvpath
	Write-Host -ForegroundColor Cyan `r`n$_
	$csvcontent | foreach {
        if(([datetime]$_.'Last Success Time').AddHours(1) -gt (Get-Date)) { Write-Host -ForegroundColor Green ($_.'Source DSA')($_.'Naming Context')($_.'Last Success Time') }
        else {  Write-Host -ForegroundColor Red ($_.'Source DSA')($_.'Naming Context')($_.'Last Success Time') }
    }
} 
if(Test-Path $csvpath) { Remove-Item $csvpath }

Si, au départ, je pensais avoir à manipuler le texte renvoyé par la commande pour extraire les bonnes informations, la commande repadmin avec l'argument showrepl accepte le paramètre -csv pour réaliser un export qui est ensuite facilement interprétable par Powershell.

Vous pouvez trouver toute la documentation nécessaire à l'utilisation de la commande repadmin sur le site de Microsoft.

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.