Powershell : obtention des liens des GPO

Si la console gpmc.msc permet de parcourir toutes les GPO ainsi que leurs liens, cela n’est pas directement possible avec Powershell et la commande Get-GPO qui ne retournera que les informations de base de la GPO.

Afin de pouvoir obtenir les liens dans Powershell, il faut ruser un peu ; dans les deux scripts Powershell suivants, j’ai donc utilisé une technique de sioux consistant à réaliser un rapport au format XML de la GPO (grâce à Get-GPOReport) et à parcourir ledit fichier XML pour y identifier les liens grâce aux balises <LinksTo>.

Ce premier script liste toutes les GPO qui n’ont aucun lien, activé ou non :

Write-Host "Unlinked GPO listing script"
 Write-Host "==========================="
 Write-Host "Working… Please wait.`r`n"
 $GPOlist = Get-GPO -all
 foreach($GPO in $GPOList){
     [xml]$xmlreport = Get-GPOReport -Guid $GPO.Id -Report xml
     if($xmlreport.GPO.LinksTo.Count -eq 0){ Write-Host $GPO.DisplayName ; $count+=1}
 }
 Write-Host "`r`nUnlinked GPO count:"$count
 Write-Host "GPO parsed:"$gpolist.Length
 Write-Host "Done."

Ce deuxième script attend en paramètre le nom d’une GPO et retourne dans la console Powershell les objets auxquels cette GPO est liée. Par exemple, on appellera le script ainsi : .\win_gpolinks.ps1 -gpo « SRV-2016-Security-V3 »

Param([string]$gpoinput)
 $gpo = Get-GPO $gpoinput
 [xml]$report = Get-GPOReport -Guid $gpo.Id -Report xml
 foreach($link in $report.GPO.LinksTo){
     Write-Host $link.SOMPath
 }

A noter qu’il est nécessaire d’avoir les RSAT d’installés sur la station (si ce n’est pas un DC) depuis laquelle le script est exécuté ; il peut également être nécessaire d’appeler Import-Module GroupPolicy pour exécuter les cmdlets Get-GPO et Get-GPOReport.

Powershell : extraction des mots de passe BitLocker

Comme j’en avais parlé il y a presque un an déjà dans ce billet, il est possible de configurer BitLocker pour qu’il stocke les mots de passe des volumes chiffrés dans l’ActiveDirectory, en lien avec l’objet Ordinateur correspondant.

BitLocker stocke dans l’ActiveDirectory un objet de type msFVE-RecoveryInformation ; l’objet Ordinateur est donc le père. On peut retrouver toutes les informations en ouvrant un ADSI Edit et en allant sur l’objet Ordinateur :

Les différents mots de passe ont pour canonical name une concaténation de leur date de création et de différentes propriétés.

Dans les propriétés de l’objet, on retrouve donc l’attribut msFVE-RecoveryPassword que je vais récupérer dans le script Powershell ci-dessous ; ce script génère la liste de tous les objets Ordinateur et leurs objets msFVE-RecoveryInformation associés pour ensuite écrire le contenu dans un fichier CSV. Ainsi, ce script permet de réaliser une copie de sauvegarde des mots de passe et de ce qui est nécessaire pour déverrouiller les disques chiffrés des stations jointes au domaine. Naturellement, on prendra soin de bien protéger ce fichier et son accès. Le script est téléchargeable dans une version commentée à la fin de l’article.

Requires -RunAsAdministrator
 Add-Type -AssemblyName System.Windows.Forms
 $fileExpl = New-Object System.Windows.Forms.SaveFileDialog
 $fileExpl.ValidateNames = $true
 $fileExpl.CreatePrompt = $false
 $fileExpl.OverwritePrompt = $true
 $fileExpl.RestoreDirectory = $false
 $fileExpl.InitialDirectory = "$env:userprofile"
 Write-Host "WARNING : This script will extract from your ActiveDirectory all the BitLocker recovery keys and passwords. This is CRITICAL DATA as you would not want unauthorized users to be able to unlock the protected drives. Please ensure the exported data is stored somewhere safe." -Foreground "Red"
 Write-Host "Please press ENTER to start."
 Read-Host
 $global:tempfile = "$env:temp\temp-bl.csv"
 if(Test-Path $tempfile){ Remove-Item $tempfile }
 $dump = "ComputerName,DistinguishedName,msFVE-RecoveryPassword"
 Add-Content -Value $dump -path $tempfile
 $complist = Get-AdComputer -Filter *
 foreach($comp in $complist){
     $BLInfo = Get-AdObject -Filter 'objectClass -eq "msFVE-RecoveryInformation"' -SearchBase $comp.DistinguishedName -Properties 'msFVE-RecoveryPassword'
     foreach($BL in $BLInfo){
         $dn =$BL.DistinguishedName
         $dump = $comp.Name+","+"'"+$dn+"'"+","+$BL.'msFVE-RecoveryPassword'
         Add-Content -Value $dump -Path $tempfile
     }
 }
 $fileExpl.DefaultExt = "csv"
 $fileExpl.Filter = "Comma-separated values file (.csv)|.csv"
 $fileExpl.Title = "Export data to CSV file"
 $fileExpl.Filename = ""
 $fileExpl.ShowDialog() | out-null
 if ($fileExpl.Filename -ne "") {
     Copy-Item $global:tempfile -Destination $fileExpl.Filename
     if ($?){ Remove-Item $global:tempfile ; Write-Host "Data exported to"$fileExpl.Filename }
     else { Write-Host "Couldn't save the CSV file to the specified location." ; Remove-Item $global:tempfile }
 }
 else { Remove-Item $global:tempfile ; Write-Host "Data export aborted." }

Ce fichier CSV peut ensuite être importé dans Excel par exemple, en le convertissant :

Powershell : listing et filtre des tâches planifiées sur de multiples serveurs

Ayant pu enfin appliquer par GPO un traitement que j’effectuais par script appelé par une tâche planifiée quotidiennement, j’ai dû procéder au listing complet des serveurs sur lesquels cette tâche était créée afin de pouvoir procéder à sa suppression. Ne pouvant pas me connecter un à un sur chaque machine pour vérifier la présence ou non, j’ai dû concevoir un script me permettant de lister les tâches planifiées qui portent mon compte d’utilisateur en auteur sur tout un scope de serveurs.

Voici le snippet que j’ai écrit :

$complist = Get-AdComputer -filter * -SearchBase "OU=SERVERS,DC=DUNDERMIFFLIN,DC=local"
foreach ($comp in $complist) {
Write-Host "`r`nServer:"$comp.DNSHostName
Invoke-Command -ComputerName $comp.DNSHostName -ScriptBlock { Get-ScheduledTask | Where-Object { $_.Author -eq "DUNDERMIFFLIN\mscott" } }
}

Ce script renvoie donc toutes les tâches planifiées que l’utilisateur mscott a créé sur chacun des serveurs présents dans la liste retournée par la requête AD. Il est possible de moduler ce que l’on souhaite obtenir en changeant le filtre ou le chemin LDAP dans la requête Get-AdComputer, sans oublier les propriétés des tâches planifiées dans la boucle Where-Object.

A noter que Get-ScheduledTask ne fonctionne qu’à partir de Windows Server 2012. Si jamais l’instruction Invoke-Command renvoie un message d’erreur propre à WinRM et que le contenu de l’instruction fonctionne en local, il vous faut activer l’administration Powershell à distance grâce à la commande Enable-PSremoting.

Utilisation de Get-ACL sur une arborescence ActiveDirectory

Cet article fait plus ou moins écho à celui d’hier où j’explique comment accorder des droits spécifiques à un utilisateur ou un groupe sur une OU ActiveDirectory. Get-ACL est une instruction Powershell permettant de retourner les permissions d’un fichier, répertoire ou même clef de registre. Par défaut, Get-ACL ne permet pas de retourner les autorisations d’un objet ActiveDirectory, mais il existe une technique de sioux permettant d’utiliser cette instruction sur un AD.

Sur une station ayant les RSAT d’installés ou bien directement sur un contrôleur de domaine, il suffit d’importer le module AD pour Powershell :

Import-Module ActiveDirectory

Ensuite, se placer dans l’arborescence AD :

Set-Location AD:

Un simple dir permet de retourner des informations nous confirmant que nous sommes bien dans l’AD.

Lancer un Get-ACL à la racine de AD: n’aura aucun effet. Il est tout de même nécessaire d’indiquer un chemin à la commande ; on utilisera alors le DistingushedName. Par exemple, pour récupérer l’intégralité des permissions à la racine du domaine (attention, ce n’est pas récursif !) :

(Get-Acl (Get-ADDomain).DistinguishedName).Access

Ainsi, il est possible de se concocter un petit snippet si l’on souhaite parcourir tout l’AD à la recherche de privilèges accordés à un groupe ou à un utilisateur sur des OU, par exemple :

Import-Module ActiveDirectory
Set-Location AD:
$oulist = Get-ADOrganizationalUnit -Filter * | select DistinguishedName
foreach($ou in $oulist){
$privileges = (Get-Acl $ou.DistinguishedName).Access | Where-Object { $_.IdentityReference -eq "DUNDERMIFFLIN\gclooney" }
if($privileges -ne $null) { Write-Host "OU:"$ou.DistinguishedName ; $privileges }
}

Par exemple, ayant accordé les droits à l’utilisateur gclooney sur l’OU End-Users, il récupère par héritage ces mêmes privilèges sur les 3 OU enfant.

Cela se vérifie avec le snippet plus haut :

Ce code peut naturellement être modifié pour interroger des groupes ou des utilisateurs. Par exemple, je vais octroyer des droits au groupe US-Presidents sur le groupe Actors. La capture suivante montre bien que le droit WriteProperty a été attribué.

En jouant sur la clause Where-Object de ce snippet, on peut modifier le filtre de recherche. Par exemple, afin rechercher tous les utilisateurs ayant le droit WriteProperty sur un objet défini, il suffit de modifier le snippet :

$grplist = Get-ADgroup -Identity "Actors" | select DistinguishedName
$privileges = (Get-Acl $grp.DistinguishedName).Access | Where-Object { $_.ActiveDirectoryRights -like "*WriteProperty*" }

Au final, la seule limite de Get-Acl et du code que l’on adapte autour, ce sont les informations que l’on souhaite récupérer et les actions éventuelles à mener pour cadrer au mieux les privilèges attribués au fil du temps !