### FILE: README.md # macOS ADE Enrollment Diagnostics Toolkit Diagnose and fix slow or failed app/script installs during macOS Automated Device Enrollment (ADE) in Microsoft Intune. ## The Three-Phase Enrollment Reality 1. **Phase 1 - Setup Assistant & Check-in Trigger**: APNs push wakes the device to check in with Intune. If blocked (firewall/proxy), device falls back to an ~8hr polling interval. 2. **Phase 2 - App Assignment Evaluation**: Apps/scripts assigned to **User Groups** cannot install pre-login (no identity context yet). Must be assigned to **Device Groups**. 3. **Phase 3 - Script Execution & Status Telemetry**: Shell scripts run serially with no native dependency chaining. Build orchestrator scripts instead of relying on Intune sequencing. ## Root Causes Covered - **APNs reachability** (17.0.0.0/8, ports 443/2195/2196/5223) blocked by SSL-inspecting proxies - **Assignment scoped to User Group** instead of Device Group - **Serial script execution** with no dependency chaining - **Expired APNs certificate** - **VLAN-specific firewall exceptions** missing on onboarding networks ## Repository Structure ``` /scripts/ PowerShell diagnostic & remediation scripts /scripts/macos-local/ Bash commands for local Mac log correlation /src/ PHP SaaS dashboard (M365 SaaS Engine integration) /docs/ Diagnostic walkthrough reference ``` ## Usage See individual script headers for `.SYNOPSIS`, `.DESCRIPTION`, and `.EXAMPLE` usage. ## Author **Souhaiel Morhag** Company: MSEndpoint.com Blog: https://msendpoint.com Academy: https://app.msendpoint.com/academy LinkedIn: https://linkedin.com/in/souhaiel-morhag GitHub: https://github.com/Msendpoint License: MIT ### FILE: scripts/Get-IntuneMacOSAppInstallStatus.ps1 <# .SYNOPSIS Retrieves per-device macOS app install status from Intune via Microsoft Graph (beta). .DESCRIPTION Queries the beta Graph endpoint deviceAppManagement/mobileApps/{id}/deviceStatuses to pull per-device install states (Pending, Failed, Not Applicable, Installed) for a given Intune macOS app deployment. Helps quickly triage whether a 'slow install' ticket is actually an assignment scoping mismatch (Not Applicable), a genuine failure, or a check-in delay. Requires an existing authenticated Microsoft Graph session (Connect-MgGraph) with DeviceManagementApps.Read.All scope, or a valid bearer token. .PARAMETER AppId The Intune mobileApp object ID to query device install statuses for. .PARAMETER AccessToken Optional bearer token. If not supplied, script assumes Connect-MgGraph has already been run and uses Invoke-MgGraphRequest. .EXAMPLE .\Get-IntuneMacOSAppInstallStatus.ps1 -AppId "a1b2c3d4-1234-5678-90ab-cdef12345678" .EXAMPLE .\Get-IntuneMacOSAppInstallStatus.ps1 -AppId "a1b2c3d4-1234-5678-90ab-cdef12345678" -AccessToken $token .NOTES Author: Souhaiel Morhag Company: MSEndpoint.com Blog: https://msendpoint.com Academy: https://app.msendpoint.com/academy LinkedIn: https://linkedin.com/in/souhaiel-morhag GitHub: https://github.com/Msendpoint License: MIT IMPORTANT: This endpoint is BETA-ONLY. Microsoft can change its schema without a deprecation notice. Do not ship beta dependencies into change-controlled production runbooks without a documented exception. All calls are wrapped in try/catch for this reason. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$AppId, [Parameter(Mandatory = $false)] [string]$AccessToken ) function Test-GraphModule { if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication)) { throw "Microsoft.Graph.Authentication module not found. Install with: Install-Module Microsoft.Graph -Scope CurrentUser" } } try { Test-GraphModule $endpoint = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$AppId/deviceStatuses" Write-Verbose "Querying beta endpoint: $endpoint" if ($AccessToken) { $headers = @{ Authorization = "Bearer $AccessToken" } $response = Invoke-RestMethod -Uri $endpoint -Headers $headers -Method Get -ErrorAction Stop } else { if (-not (Get-MgContext)) { Connect-MgGraph -Scopes "DeviceManagementApps.Read.All" -ErrorAction Stop | Out-Null } $response = Invoke-MgGraphRequest -Method GET -Uri $endpoint -ErrorAction Stop } $deviceStatuses = $response.value if (-not $deviceStatuses -or $deviceStatuses.Count -eq 0) { Write-Warning "No device status records returned for AppId '$AppId'. Verify the app ID and that assignments exist." return } $results = foreach ($status in $deviceStatuses) { [PSCustomObject]@{ DeviceName = $status.deviceName UserName = $status.userName InstallState = $status.installState LastSyncDate = $status.lastSyncDateTime ErrorCode = $status.errorCode } } $pendingCount = ($results | Where-Object { $_.InstallState -eq 'pending' }).Count $notApplicable = ($results | Where-Object { $_.InstallState -eq 'notApplicable' }).Count $failedCount = ($results | Where-Object { $_.InstallState -eq 'failed' }).Count Write-Host "--- Install Status Summary for App $AppId ---" -ForegroundColor Cyan Write-Host "Pending: $pendingCount" Write-Host "Not Applicable: $notApplicable (likely assignment scope mismatch - check group type/OS filter)" -ForegroundColor Yellow Write-Host "Failed: $failedCount" -ForegroundColor Red $results | Format-Table -AutoSize return $results }