Rechercher des ordinateurs dans toute une forêt Active Directory avec PowerShell (GC 3268/3269, filtres LDAP, script)

Besoin de retrouver des ordinateurs à l’échelle de toute une forêt Active Directory depuis PowerShell — même si votre poste n’est pas joint au domaine ciblé ? Voici une méthode fiable et sécurisée basée sur le catalogue global (ports 3268/3269), avec scripts prêts à l’emploi et bonnes pratiques.

Sommaire

Vue d’ensemble de la question

Dans la console Active Directory Users & Computers, l’option « Entire Directory » interroge le catalogue global (Global Catalog, GC) afin de chercher dans tous les domaines d’une même forêt. L’objectif ici est de reproduire ce comportement en PowerShell, idéalement sans être membre des domaines interrogés : il suffit de disposer de droits de lecture et d’un accès réseau aux contrôleurs de domaine qui hébergent le GC.

Deux approches efficaces

Approche express (recommandée)

Cette approche tire parti du préfixe GC:// compris par le module ActiveDirectory. Elle est concise et couvre toute la forêt sans que vous ayez à définir une base de recherche spécifique.

Import-Module ActiveDirectory

# 1) Saisir des identifiants d'un compte ayant au moins "Read" dans la forêt ciblée

$cred = Get-Credential  # DOMAINE\utilisateur + mot de passe

# 2) Choisir un contrôleur qui fait office de Global Catalog dans la forêt

$gc = (Get-ADForest -Credential $cred).GlobalCatalogs | Select-Object -First 1

# 3) Interroger le GC sur l'ensemble de la forêt (aucun SearchBase requis)

Get-ADComputer -Server "GC://$gc" `               -LDAPFilter "(objectClass=computer)"`
-Credential $cred `
-Properties DNSHostName,OperatingSystem,LastLogonDate |
Select-Object Name, DNSHostName, OperatingSystem, LastLogonDate 

Pourquoi « GC:// » ? Le préfixe demande à PowerShell d’utiliser le catalogue global comme source de vérité ; la recherche se propage alors à travers tous les domaines de la forêt. Évitez dans ce cas de fixer -SearchBase sur un DN de domaine précis, sinon vous restreindrez la portée à ce seul domaine.

Approche pas‑à‑pas (détaillée)

Si vous préférez dérouler chaque étape, ou si vous devez expliquer la démarche, suivez ce guide détaillé.

ÉtapeCommandes & explicationsNotes utiles
Charger le module ADImport-Module ActiveDirectoryRSAT (ou ADWS) requis. Sur un serveur membre, le module est souvent déjà présent.
Récupérer des identifiants sécurisés$cred = Get-Credential # DOMAINE\utilisateurÉvite tout mot de passe en clair. Fonctionne même si votre machine n’est pas jointe au domaine.
Localiser un contrôleur catalogue global$gc = (Get-ADForest -Credential $cred).GlobalCatalogs | Select-Object -First 1Le GC voit tous les domaines de la forêt. Les ports usuels : 3268 (LDAP), 3269 (LDAPS).
Définir (ou non) une base de recherche# <Optionnel> Si vous utilisez "GC://", laissez PowerShell faire : # Pas de -SearchBase = recherche forêt entière. # Si vous ciblez un domaine spécifique, utilisez son DN : $rootDN = (Get-ADRootDSE -Server $gc -Credential $cred).defaultNamingContextAvec GC://, omettre -SearchBase garantit une portée « Entire Directory ».
Lancer la recherche# Recherche forêt entière (recommandé) Get-ADComputer -Server "GC://$gc" ` -LDAPFilter "(objectClass=computer)" ` -Credential $cred ` -Properties DNSHostName,OperatingSystem | Select-Object Name, DNSHostName, OperatingSystem # Variante ciblant un domaine précis (moins large) Get-ADComputer -LDAPFilter "(objectClass=computer)" ` -SearchBase $rootDN -SearchScope Subtree` -Server "$gc:3268" -Credential $cred | Select-Object Name, DNSHostName, OperatingSystemAjoutez un export si besoin : | Export-Csv .\Ordinateurs.csv -NoTypeInformation -Encoding UTF8

Points clés à retenir

  • Port 3268 : force l’interrogation du catalogue global (cherche dans toute la forêt). Utilisez 3269 pour LDAPS si une couche TLS est exigée.
  • Filtre LDAP : adaptez le critère : (cn=*WEB*), (dNSHostName=*.corp.local), (operatingsystem=*Server 2022*), etc.
  • Évolutivité : filtrez côté serveur avec -LDAPFilter, limitez les attributs (-Properties) et utilisez -ResultSetSize si nécessaire.
  • Pas de privilèges élevés : des droits de lecture suffisent sur le GC ; nul besoin d’être admin du domaine.

Cheat‑sheet des filtres utiles

ObjectifExemple de filtre LDAPRemarques
Toutes les machines(objectClass=computer)Point de départ classique.
Nom partiel(&(objectClass=computer)(cn=*PROD*))Jokers * acceptés.
Par OS(&(objectClass=computer)(operatingsystem=*Server 2022*))operatingsystem est dans le GC.
Hôtes joints (DNS)(&(objectClass=computer)(dNSHostName=*.eu.corp))Utile pour des suffixes DNS par région.
Dernière connexion récente(&(objectClass=computer)(lastLogonTimestamp>=132669792000000000))Valeur en FILETIME. lastLogonTimestamp est répliqué (granularité ~14 jours).
Machines désactivées(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=2))Test du flag ACCOUNTDISABLE.

Script prêt à l’emploi

Le script ci‑dessous encapsule la recherche « Entire Directory » et gère la tolérance aux pannes entre plusieurs GC.

function Find-ADForestComputer {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory=$false)]
    [string] $LDAPFilter = '(objectClass=computer)',

```
[Parameter(Mandatory=$false)]
[string[]] $Properties = @('Name','DNSHostName','OperatingSystem','LastLogonDate','whenCreated','DistinguishedName'),

[Parameter(Mandatory=$false)]
[int] $ResultSetSize = 0,  # 0 = illimité (par défaut)

[Parameter(Mandatory=$false)]
[int] $PageSize = 1000,

[Parameter(Mandatory=$false)]
[pscredential] $Credential
```

)

if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
throw 'Le module ActiveDirectory (RSAT) est requis.'
}
Import-Module ActiveDirectory -ErrorAction Stop

if (-not $Credential) {
$Credential = Get-Credential -Message 'Identifiants avec droits de lecture (Read) sur la forêt'
}

$forest = Get-ADForest -Credential $Credential
$gcs = $forest.GlobalCatalogs | Sort-Object -Unique
if (-not $gcs) { throw 'Aucun Global Catalog trouvé dans la forêt.' }

foreach ($gc in $gcs) {
try {
Write-Verbose "Interrogation du GC $gc…"
$args = @{
Server         = "GC://$gc"
LDAPFilter     = $LDAPFilter
Credential     = $Credential
Properties     = $Properties
ResultSetSize  = $ResultSetSize
ResultPageSize = $PageSize
}
$res = Get-ADComputer @args
if ($res) { return $res }
} catch {
Write-Warning "Échec sur $gc : $($_.Exception.Message)"
continue
}
}

Write-Verbose 'Aucun résultat ou tous les GC ont échoué.'
return @()
}

# Exemples d'usage :

# 1) Toutes les machines avec un nom partiel

# Find-ADForestComputer -LDAPFilter '(&(objectClass=computer)(cn=*WEB*))' | Select Name,DNSHostName

# 2) Windows Server 2022 uniquement + export

# Find-ADForestComputer -LDAPFilter '(&(objectClass=computer)(operatingsystem=*Server 2022*))' |

# Select Name,OperatingSystem,DNSHostName,LastLogonDate |

# Export-Csv .\Serveurs2022.csv -NoTypeInformation -Encoding UTF8

Comprendre le catalogue global

  • Rôle : le GC héberge un index de tous les objets de tous les domaines de la forêt. Il réplique un sous‑ensemble d’attributs (Partial Attribute Set).
  • Ports : 3268 (LDAP en clair dans le tunnel Kerberos/NTLM), 3269 (LDAPS/TLS). Dans des environnements stricts, préférez 3269 et assurez‑vous d’avoir une PKI et des certificats valides sur les DC.
  • Limites : certains attributs ne sont pas présents dans le GC. Si vous avez besoin d’un attribut hors PAS, faites une deuxième lecture ciblée contre le DC du domaine de l’objet (en vous servant de son DistinguishedName ou du CanonicalName).

Bonnes pratiques et compléments utiles

  • Identifiants : n’écrivez jamais de mot de passe en clair. Utilisez Get-Credential, SecretManagement ou un coffre (Azure Key Vault, KeePass, etc.).
  • Droits : un compte standard avec la permission Read suffit. La lecture du GC ne nécessite pas de groupes privilégiés.
  • Filtrer tôt : mettez le maximum de conditions dans -LDAPFilter pour réduire le volume renvoyé.
  • Limiter les attributs : ne demandez que ce dont vous avez besoin via -Properties. Exemple : DNSHostName,OperatingSystem,LastLogonDate.
  • Paginer : pour de très grands annuaires, fixez -ResultPageSize 1000 et utilisez -ResultSetSize pour borner le nombre total de résultats.
  • Export : systématisez un export CSV encodé en UTF‑8 pour la réutilisation : | Export-Csv .\InventaireAD.csv -NoTypeInformation -Encoding UTF8.
  • Alternative sans module AD : DirectorySearcher (.NET) ou ADSI avec le préfixe GC://, pratique sur des machines sans RSAT.
  • Compatibilité : avec RSAT ≥ 5.1, -Server "GC://<hôte>" est parfaitement supporté par les cmdlets Get-AD*.

Exemples concrets

Inventaire rapide par système d’exploitation

Find-ADForestComputer -LDAPFilter '(&(objectClass=computer)(operatingsystem=*Windows Server*))' -Properties Name,OperatingSystem |
  Group-Object OperatingSystem |
  Sort-Object Count -Descending |
  Select-Object @{n='OS';e={$_.Name}}, Count

Liste des ordinateurs inactifs depuis plus de 90 jours

Le GC réplique lastLogonTimestamp (granularité d’environ 9 à 14 jours). Exemple :

$days = 90
$threshold = (Get-Date).AddDays(-$days)

Find-ADForestComputer -LDAPFilter '(&(objectClass=computer)(lastLogonTimestamp=*))' -Properties Name,LastLogonDate,DistinguishedName |
Where-Object { $_.LastLogonDate -lt $threshold } |
Select-Object Name, LastLogonDate, DistinguishedName |
Export-Csv .\Ordinateurs_Inactifs.csv -NoTypeInformation -Encoding UTF8

Recherche par suffixe DNS

Find-ADForestComputer -LDAPFilter '(&(objectClass=computer)(dNSHostName=*.emea.corp.local))' |
  Select-Object Name, DNSHostName

Alternative sans RSAT : .NET DirectorySearcher

Utile sur une machine sans module ActiveDirectory ; fonctionne avec le préfixe GC:// et supporte LDAPS si vous en avez besoin.

# Recherche forêt entière via DirectorySearcher
$cred = Get-Credential

# Connexion GC (LDAP 3268). Pour LDAPS, remplacez par "GC://:3269" et ajoutez AuthenticationTypes.SecureSocketsLayer.

$gcHost = (([DirectoryServices.ActiveDirectory.Forest]::GetForest()).GlobalCatalogs)[0].Name
$root = New-Object System.DirectoryServices.DirectoryEntry("GC://$gcHost", $cred.UserName, $cred.GetNetworkCredential().Password)
$ds   = New-Object System.DirectoryServices.DirectorySearcher($root)
$ds.PageSize = 1000
$ds.Filter = "(&(objectClass=computer)(cn=*))"
$ds.PropertiesToLoad.AddRange(@('cn','dNSHostName','operatingSystem')) | Out-Null

$computers = $ds.FindAll() | ForEach-Object {
[PSCustomObject]@{
Name            = $*.Properties['cn']
DNSHostName     = $*.Properties['dnshostname']
OperatingSystem = $_.Properties['operatingsystem']
}
}
$computers | Format-Table -AutoSize

Performance : recommandations

  • Filtrage serveur : préférez -LDAPFilter (ou le filtre DirectorySearcher) à un Where-Object côté client.
  • Attributs : ne demandez que l’essentiel pour éviter des paquets LDAP volumineux.
  • Par lots : si vous enchaînez des enrichissements par domaine, stockez d’abord Name/DistinguishedName, puis relisez les attributs non répliqués par domaine pour amortir le coût réseau.
  • Paralléliser prudemment : sur des forêts immenses, vous pouvez paralléliser par OU ou par domaine, en respectant les limites du DC et de votre réseau (ne saturez pas les GC).

Dépannage : erreurs fréquentes

SymptômeCause probableCorrectif
The server is not operationalPort 3268/3269 bloqué, résolutions DNS incorrectesOuvrir les ports vers les GC ; vérifier le DNS du poste et les FQDN des DC/GC
Cannot find an object with identity ...Filtre trop restrictif ou -SearchBase réduit la portée au seul domaineSupprimer -SearchBase avec GC:// ; détendre le filtre
Logon failure / Access deniedCompte sans droits de lecture, mot de passe erroné, horloge désynchroniséeUtiliser un compte de la forêt, corriger l’heure (Kerberos), tester avec nltest /dsgetdc:
Attributs vides alors qu’ils existentAttribut hors Partial Attribute Set du GCRelire l’objet sur son DC de domaine avec -Server ciblé

Bonnes pratiques de sécurité

  • Chiffrement : si l’organisation l’exige, utilisez LDAPS (port 3269) et des certificats validés par la PKI interne.
  • Moindre privilège : stockez vos scripts sans identifiants et demandez les secrets à l’exécution (Get-Credential) ou via un coffre.
  • Traçabilité : consignez l’usage et l’export (nom du fichier, horodatage) dans un log simple.

FAQ éclair

Est‑ce que le GC couvre des forêts différentes ? Non. Le GC est propre à une forêt. Pour des recherches inter‑forêts, interrogez chaque forêt (et son GC) séparément.

Dois‑je être membre d’un domaine ? Non, tant que vous fournissez des identifiants valides et que le réseau/DNS permet d’atteindre les GC.

Quelle est la différence entre -Filter et -LDAPFilter ? -Filter est la syntaxe PowerShell des cmdlets AD ; -LDAPFilter parle nativement LDAP. Pour des cas avancés (bitwise, FILETIME, jokers), -LDAPFilter est souvent plus précis.

Résumé

Pour reproduire « Entire Directory » en PowerShell : ciblez un hôte Global Catalog, utilisez -Server "GC://<hôte>", évitez de fixer -SearchBase si vous souhaitez bien couvrir toute la forêt, filtrez côté serveur avec -LDAPFilter et ne rapatriez que les attributs utiles. Les exemples et le script Find-ADForestComputer ci‑dessus fournissent une base solide, sécurisée et performante pour vos inventaires et vos recherches.

Sommaire