ActiveDirectory/Get-HybridSyncReport.ps1
|
<#
.SYNOPSIS Reports hybrid identity sync status between on-premises Active Directory and Entra ID. .DESCRIPTION Queries Microsoft Graph for organization properties to determine if hybrid sync (Entra Connect or Cloud Sync) is enabled and when the last sync occurred. Optionally queries on-premises Active Directory (if the ActiveDirectory module is available) for domain and forest information. Essential for M365 security assessments, hybrid identity reviews, and migration planning. Requires Microsoft Graph connection with Organization.Read.All permission. On-premises AD queries require the ActiveDirectory PowerShell module and domain connectivity. .PARAMETER IncludeOnPremAD Attempt to query on-premises Active Directory for domain and forest details. Requires the ActiveDirectory PowerShell module and network connectivity to a domain controller. If the module is not available, a warning is emitted and the script continues without on-prem data. .PARAMETER OutputPath Optional path to export results as CSV. If not specified, results are returned to the pipeline. .EXAMPLE PS> . .\Common\Connect-Service.ps1 PS> Connect-Service -Service Graph -Scopes 'Organization.Read.All' PS> .\ActiveDirectory\Get-HybridSyncReport.ps1 Displays hybrid sync status from Entra ID organization properties. .EXAMPLE PS> .\ActiveDirectory\Get-HybridSyncReport.ps1 -IncludeOnPremAD -OutputPath '.\hybrid-sync-report.csv' Includes on-premises AD domain/forest info and exports to CSV. .EXAMPLE PS> .\ActiveDirectory\Get-HybridSyncReport.ps1 -IncludeOnPremAD -Verbose Shows detailed progress output including on-premises AD query attempts. #> [CmdletBinding()] param( [Parameter()] [switch]$IncludeOnPremAD, [Parameter()] [ValidateNotNullOrEmpty()] [string]$OutputPath ) $ErrorActionPreference = 'Stop' # Verify Graph connection if (-not (Assert-GraphConnection)) { return } # Ensure required Graph submodule is loaded (PS 7.x does not auto-import) Import-Module -Name Microsoft.Graph.Identity.DirectoryManagement -ErrorAction Stop # Retrieve organization details try { Write-Verbose "Retrieving organization details from Microsoft Graph..." $organizations = Get-MgOrganization } catch { Write-Error "Failed to retrieve organization details: $_" return } # Attempt on-premises AD queries if requested $onPremDomainName = $null $onPremForestName = $null if ($IncludeOnPremAD) { try { $adModule = Get-Module -Name ActiveDirectory -ListAvailable -ErrorAction SilentlyContinue if ($adModule) { Write-Verbose "ActiveDirectory module found. Querying on-premises AD..." try { $adDomain = Get-ADDomain -ErrorAction Stop $onPremDomainName = $adDomain.DNSRoot Write-Verbose "On-premises domain: $onPremDomainName" } catch { Write-Warning "Failed to query on-premises AD domain: $_" } try { $adForest = Get-ADForest -ErrorAction Stop $onPremForestName = $adForest.Name Write-Verbose "On-premises forest: $onPremForestName" } catch { Write-Warning "Failed to query on-premises AD forest: $_" } } else { Write-Warning "ActiveDirectory PowerShell module is not installed. Skipping on-premises AD queries." } } catch { Write-Warning "Error checking for ActiveDirectory module: $_" } } # Build the report from organization properties $report = foreach ($org in $organizations) { $onPremSyncEnabled = $org.OnPremisesSyncEnabled $lastDirSyncTime = $org.OnPremisesLastSyncDateTime $lastPasswordSyncTime = $org.OnPremisesLastPasswordSyncDateTime # Determine sync type based on available properties $syncType = if ($onPremSyncEnabled -eq $true) { if ($lastPasswordSyncTime) { 'Entra Connect (Password Hash Sync detected)' } else { 'Entra Connect or Cloud Sync' } } else { 'Cloud-only (no hybrid sync)' } # Determine if password hash sync is enabled based on last sync timestamp $passwordHashSyncEnabled = if ($lastPasswordSyncTime) { $true } else { $false } # Determine if directory sync is configured (distinct from enabled) $dirSyncConfigured = if ($null -ne $onPremSyncEnabled) { $onPremSyncEnabled } else { $false } $resultObject = [PSCustomObject]@{ TenantDisplayName = $org.DisplayName TenantId = $org.Id OnPremisesSyncEnabled = $onPremSyncEnabled LastDirSyncTime = $lastDirSyncTime DirSyncConfigured = $dirSyncConfigured PasswordHashSyncEnabled = $passwordHashSyncEnabled LastPasswordSyncTime = $lastPasswordSyncTime SyncType = $syncType OnPremDomainName = if ($onPremDomainName) { $onPremDomainName } else { 'N/A' } OnPremForestName = if ($onPremForestName) { $onPremForestName } else { 'N/A' } } $resultObject } $report = @($report) Write-Verbose "Processed hybrid sync status for $($report.Count) organization(s)" if ($OutputPath) { $report | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 Write-Output "Exported hybrid sync report ($($report.Count) record(s)) to $OutputPath" } else { Write-Output $report } |