Public/Entra/Hybrid/Get-EntraIDHybridJoinComputerInfo.ps1
|
<#
.SYNOPSIS Retrieves detailed information about Entra ID Hybrid Join configuration on the local computer. .DESCRIPTION This function gathers comprehensive diagnostics for Entra ID Hybrid Join status including: - Service Connection Point (SCP) details - Network connectivity to Microsoft Entra ID endpoints - Registry keys related to hybrid join - Scheduled tasks for Automatic Device Join - Computer certificates in AD and local store - Event logs for device registration - dsregcmd status output .PARAMETER ExportToExcel Optional path to export the results to an Excel file. .EXAMPLE Get-EntraIDHybridJoinComputerInfo Retrieves all hybrid join diagnostic information for the local computer. .EXAMPLE Get-EntraIDHybridJoinComputerInfo -ExportToExcel "C:\Reports\HybridJoinInfo.xlsx" Retrieves hybrid join information and exports it to an Excel file. .NOTES Must be run on a domain-joined computer. #> function Get-EntraIDHybridJoinComputerInfo { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [switch]$ExportToExcel ) # Initialize results collection $results = @{} Write-Host -ForegroundColor Cyan 'Looking for Service Connection Point (SCP)' $scp = Get-EntraIDHybridJoinSCP $results.SCP = $scp if (-not ($ExportToExcel.IsPresent)) { $scp } # Urls $urls = @( 'login.microsoftonline.com' 'enterpriseregistration.windows.net' 'device.login.microsoftonline.com' ) Write-Host -ForegroundColor Cyan 'Testing connectivity to Microsoft Entra ID' [System.Collections.Generic.List[Object]]$connectivityResults = @() foreach ($url in $urls) { $result = Test-NetConnection $url -Port 443 $connectivityResult = [PSCustomObject][ordered]@{ Url = $url Connected = $result.TcpTestSucceeded RemoteAddress = $result.RemoteAddress } $connectivityResults.Add($connectivityResult) if ($result.TcpTestSucceeded -eq $false) { Write-Warning -ForegroundColor Red "Failed to connect to $url" } else { Write-Host -ForegroundColor Green "Successfully connected to $url - $($result.RemoteAddress)" } } $results.Connectivity = $connectivityResults Write-Host -ForegroundColor Cyan 'Testing Microsoft Entra ID registry keys' $registryInfo = Get-EntraIDHybridJoinComputerRegistryKey $results.RegistryInfo = $registryInfo if ($registryInfo.TenantId) { Write-Host -ForegroundColor Green "TenantID: $($registryInfo.TenantId)" } if ($registryInfo.TenantName) { Write-Host -ForegroundColor Green "TenantName: $($registryInfo.TenantName)" } if ($registryInfo.OtherKeys) { Write-Host -ForegroundColor Yellow "Other registry keys: $($registryInfo.OtherKeys)" } Write-Host -ForegroundColor Cyan 'Get scheduled tasks for Workplace Join' $deviceJoinTask = Get-ScheduledTask -TaskPath '\Microsoft\Windows\Workplace Join\' -TaskName 'Automatic-Device-Join' $task = Get-ScheduledTaskInfo -TaskPath ($deviceJoinTask.TaskPath) -TaskName $deviceJoinTask.TaskName $taskInfo = [PSCustomObject][ordered]@{ TaskPath = $task.TaskPath TaskName = $task.TaskName LastTaskResult = $task.LastTaskResult LastRunTime = $task.LastRunTime NextRunTime = $task.NextRunTime } $results.ScheduledTask = $taskInfo Write-Host -ForegroundColor Cyan "Task: $($task.TaskPath)$($task.TaskName) - Task last result: $($task.LastTaskResult) - LastRunTime: $($task.LastRunTime) - NextRunTime: $($task.NextRunTime)" Write-Host -ForegroundColor Cyan 'Get usercertificate in AD computer object' $computerName = $env:COMPUTERNAME $search = [adsisearcher]"(&(ObjectClass=Computer)(cn=$env:computername))" [System.Collections.Generic.List[Object]]$adCertificates = @() try { $computer = $search.FindAll() foreach ($certificate in $computer.Properties.usercertificate) { $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]$certificate $adCertificate = [PSCustomObject][ordered]@{ DnsNameList = $cert.DnsNameList -join '|' FriendlyName = $cert.FriendlyName Subject = $cert.Subject Issuer = $cert.Issuer NotAfter = $cert.NotAfter NotBefore = $cert.NotBefore Thumbprint = $cert.Thumbprint ExpiredData = $cert.NotAfter SerialNumber = $cert.SerialNumber EnhancedKeyUsageList = $cert.EnhancedKeyUsageList -join '|' } $adCertificates.Add($adCertificate) } } catch { Write-Warning "Failed to get $computerName object from AD - $($_.Exception.Message)" } if (-not ($ExportToExcel.IsPresent)) { $adCertificates } else { $results.ADCertificates = $adCertificates } Write-Host -ForegroundColor Cyan 'Get computer certificates in Personal store' [System.Collections.Generic.List[Object]]$localEntraCertificates = @() $entraCertificates = Get-ChildItem -Path cert:\LocalMachine\My | Where-Object { $_.Issuer -like '*MS-Organization-Access*' -or $_.Issuer -like '*MS-Organization-P2P-Access*' } foreach ($certificate in $entraCertificates) { $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]$certificate $localCertificate = [PSCustomObject][ordered]@{ SerialNumber = $cert.SerialNumber FriendlyName = $cert.FriendlyName Subject = $cert.Subject Issuer = $cert.Issuer NotAfter = $cert.NotAfter NotBefore = $cert.NotBefore Thumbprint = $cert.Thumbprint ExpiredData = $cert.NotAfter EnhancedKeyUsageList = $cert.EnhancedKeyUsageList -join '|' DnsNameList = $cert.DnsNameList -join '|' } $localEntraCertificates.Add($localCertificate) } if (-not ($ExportToExcel.IsPresent)) { $localEntraCertificates } else { $results.LocalCertificates = $localEntraCertificates } Write-Host -ForegroundColor Cyan 'Get Hybrid Join details' [System.Collections.Generic.List[Object]]$DeviceRegistrationEvent = @() $eventObject = [PSCustomObject][ordered]@{ TimeCreated = $null Message = 'No event found' } try { $eventLogs = Get-WinEvent -FilterHashtable @{LogName = 'Microsoft-Windows-User Device Registration/Admin'; Id = 306 } -ErrorAction SilentlyContinue if ($eventLogs) { foreach ($event in $eventLogs) { $eventObject = [PSCustomObject][ordered]@{ TimeCreated = $event.TimeCreated Message = $event.Message } } } else { Write-Warning 'No event in Microsoft-Windows-User Device Registration/Admin was found' # Sometimes, we just need to wait after the logon # I have some issue with 360 events and Windows Hello Enterprise provisioning or not domain controller line of sight Write-Warning "You can logoff/login or wait or runStart-ScheduledTask -TaskPath '\Microsoft\Windows\Workplace Join\' -TaskName 'Automatic-Device-Join'" } $DeviceRegistrationEvent.Add($eventObject) } catch { Write-Warning "Failed to get events: $($_.Exception.Message)" $DeviceRegistrationEvent.Add($eventObject) } $results.DeviceRegistrationEvent = $DeviceRegistrationEvent Write-Host -ForegroundColor Cyan 'Get dsregcmd status' # Computer state $dsregcmdObject = Get-EntraIDRegcmd $joinType = 0 if ( $dsregcmdObject.EnterpriseJoined -eq 'YES' ) { $joinType = 4 } else { if ( $dsregcmdObject.AzureAdJoined -eq 'YES' ) { if ( $dsregcmdObject.DomainJoined -eq 'YES' ) { $joinType = 3 } else { $joinType = 1 } } else { if ( $dsregcmdObject.DomainJoined -eq 'YES' ) { if ( $dsregcmdObject.WorkplaceJoined -eq 'YES' ) { $joinType = 6 } else { $joinType = 2 } } else { if ( $dsregcmdObject.WorkplaceJoined -eq 'YES' ) { $joinType = 5 } } } } switch ($joinType) { 0 { $joinTypeText = 'Local Only' break } 1 { $joinTypeText = 'AAD Joined' break } 2 { $joinTypeText = 'Domain Only' break } 3 { $joinTypeText = 'Hybrid AAD' break } 4 { $joinTypeText = 'DRS' break } 5 { $joinTypeText = 'Local;AAD Reg' break } 6 { $joinTypeText = 'Domain;AAD Reg' break } default { $joinTypeText = "Unknown join Type - $joinType" break } } $dsregcmdObject | Add-Member -NotePropertyName 'JoinType' -NotePropertyValue $joinTypeText $results.DsRegCmd = $dsregcmdObject $dsregcmdObject # Export to Excel if requested if ($ExportToExcel.IsPresent) { Write-Verbose 'Preparing Excel export...' $now = Get-Date -Format 'yyyy-MM-dd_HHmmss' $excelFilePath = "$($env:userprofile)\$now-EntraIDHybridJoinInfo.xlsx" Write-Verbose "Excel file path: $excelFilePath" $workbook = @{} if ($results.SCP) { $workbook.SCP = @($results.SCP) } if ($results.Connectivity) { $workbook.Connectivity = $results.Connectivity } if ($results.RegistryInfo) { $workbook.RegistryInfo = @($results.RegistryInfo) } if ($results.ScheduledTask) { $workbook.ScheduledTask = @($results.ScheduledTask) } if ($results.ADCertificates -and $results.ADCertificates.Count -gt 0) { $workbook.ADCertificates = $results.ADCertificates } if ($results.LocalCertificates -and $results.LocalCertificates.Count -gt 0) { $workbook.LocalCertificates = $results.LocalCertificates } if ($results.DeviceRegistrationEvent -and $results.DeviceRegistrationEvent.Count -gt 0) { $workbook.DeviceRegistrationEvent = $results.DeviceRegistrationEvent } if ($results.DsRegCmd) { # Convert dsregcmd object to array for Excel export [System.Collections.Generic.List[Object]]$dsregArray = @() foreach ($property in $results.DsRegCmd.PSObject.Properties) { $dsregProperty = [PSCustomObject]@{ Property = $property.Name Value = $property.Value } $dsregArray.Add($dsregProperty) } $workbook.DsRegCmd = $dsregArray } # Export each section to a separate worksheet Write-Host -ForegroundColor Cyan "Exporting dynamic groups to Excel file: $excelFilePath" foreach ($sheetName in $workbook.Keys) { Write-Verbose "Exporting $sheetName to Excel..." $workbook[$sheetName] | Export-Excel -Path $excelFilePath -WorksheetName $sheetName -AutoSize -TableStyle Medium9 } } } |