{"id":51693405,"url":"https://github.com/powerstacks-corp/graph-error-finder","last_synced_at":"2026-07-16T03:33:52.767Z","repository":{"id":355656460,"uuid":"1229002535","full_name":"powerstacks-corp/graph-error-finder","owner":"powerstacks-corp","description":"PowerShell diagnostic script that collects Microsoft Graph API request data for BI for Intune customers troubleshooting slow Intune syncs with Microsoft Support.","archived":false,"fork":false,"pushed_at":"2026-07-16T00:33:35.000Z","size":66,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-16T02:20:44.514Z","etag":null,"topics":["bi-for-intune","diagnostics","graph-api","intune","microsoft-graph","powershell","powerstacks","support"],"latest_commit_sha":null,"homepage":"https://github.com/powerstacks-corp/graph-error-finder","language":"PowerShell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/powerstacks-corp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-04T15:46:02.000Z","updated_at":"2026-07-16T00:33:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/powerstacks-corp/graph-error-finder","commit_stats":null,"previous_names":["pjm-powerstacks/graph-error-finder","powerstacks-corp/graph-error-finder"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/powerstacks-corp/graph-error-finder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/powerstacks-corp%2Fgraph-error-finder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/powerstacks-corp%2Fgraph-error-finder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/powerstacks-corp%2Fgraph-error-finder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/powerstacks-corp%2Fgraph-error-finder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/powerstacks-corp","download_url":"https://codeload.github.com/powerstacks-corp/graph-error-finder/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/powerstacks-corp%2Fgraph-error-finder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35529725,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-16T02:00:06.687Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bi-for-intune","diagnostics","graph-api","intune","microsoft-graph","powershell","powerstacks","support"],"created_at":"2026-07-16T03:33:51.935Z","updated_at":"2026-07-16T03:33:52.762Z","avatar_url":"https://github.com/powerstacks-corp.png","language":"PowerShell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Graph API Error Finder\n\nA PowerShell diagnostic script that gathers hard evidence about how Microsoft Graph and the Intune reports\nExport API actually behave in your tenant: pagination, throttling, retries, page-size failures, export-job\nlatency, and silent data loss. The output is designed to be attached to a Microsoft Support case.\n\nPublished by **PowerStacks** for **BI for Intune** customers, and for anyone integrating with Intune Graph\nendpoints who needs to prove what is going wrong rather than describe it.\n\n\u003e **Use this when:** your Intune sync is slow, intermittently failing, returning HTTP 429/500/502/503/504,\n\u003e or quietly returning less data than it should, and you need reproducible evidence for Microsoft.\n\n**Current version: 4.5.1.** The script's `.NOTES` block carries the full version history, including the\nfield observations behind each change. That is the authoritative changelog; this README describes what the\nscript does today.\n\n---\n\n## What the script does\n\nIt authenticates as a service principal and runs two families of diagnostics.\n\n### 1. Paged Graph collection endpoints\n\nExercises the endpoints BI for Intune reads, at production shape and page size:\n\n| Endpoint | What it exercises |\n|---|---|\n| `deviceAppManagement/mobileApps` | `$expand=assignments,categories` at production page size, plus isolation variants (assignments only, categories only, no expand, and the proposed fix shape) to separate payload composition from page size |\n| `deviceManagement/deviceManagementScripts` | Platform scripts with `assignments` expanded |\n| `deviceManagement/deviceHealthScripts` | Proactive remediations with `assignments` expanded |\n\nFor each page it records HTTP status, per-attempt and per-page timing, `Retry-After` handling, backoff, and\nretry counts. Two behaviors get special handling:\n\n- **Adaptive page size.** On timeout-class failures the script halves `$top` and retries immediately (no\n  backoff, because a smaller request is a different request, not a transient one). After 3 consecutive pages\n  need reduction it rides at the reduced size instead of relearning the same failure on every page. This is\n  configurable, including a pure evidence mode that retries every page at full size to maximize\n  fail/succeed pairs for escalation.\n- **Problems (HTTP 200, but wrong).** A page that returns fewer rows than `$top` **and** carries an\n  `@odata.nextLink` is recorded as a small-result-set anomaly: Graph claims more data exists while handing\n  back a partial page, which silently breaks any consumer that treats `count \u003c $top` as end-of-data. The\n  detector runs against every paged call, so new endpoints exhibiting it are caught automatically.\n\n### 2. Intune reports Export API\n\nExercises `deviceManagement/reports/exportJobs`, the pipeline BI for Intune's compliance reporting uses:\njob creation, queue and processing latency, download, and CSV row validation. It flags stuck jobs, failed\njobs, and jobs that complete but return the wrong number of rows.\n\nIt also reproduces the silent-incomplete-data pattern directly: an **unfiltered baseline export** is\ncompared against **per-ID filtered exports** (the production access pattern). Because a live tenant drifts\nwhile the jobs run, the baseline is **bracketed**: a second unfiltered export runs after the filtered jobs,\nand a mismatch is only reported as deterministic when the filtered count falls outside *both* baselines.\nDrift-consistent mismatches are recorded separately rather than reported as data loss.\n\n### Remediation run states\n\n`deviceRunStates` is the largest dataset in this family (hundreds of scripts across tens of thousands of\ndevices), and its export report requires a `PolicyId` filter, so no bulk ground truth exists. The script\ntherefore tests three witnesses per sampled script: the filtered export, the paged endpoint, and the\nscript's own `runSummary` counters. Export versus paged is the hard comparison, and disagreement is flagged.\n\n### Optional scale test\n\n`$doScaleTest = $True` queries `/assignments` for every item individually, capturing per-item response\ntimes, assignment counts, \"All Devices\" / \"All Users\" scope flags, and the slowest items. Useful for showing\nwhether one noisy item is causing backend pressure. It is slow, and off by default.\n\n---\n\n## Output\n\nEverything lands in `$LogDir` (default `C:\\Temp`), timestamped per run:\n\n| File | Contents |\n|---|---|\n| `GraphDiag_\u003cts\u003e.log` | The run log, in **CMTrace format**: millisecond timestamps, severity types, and bias, so failures and warnings colour-code in CMTrace. Open it with CMTrace or any text editor. |\n| `RetryEvents_\u003cts\u003e.csv` | Every retry: endpoint, page, attempt, HTTP status, wait, request id, and the scale unit that served it |\n| `Problems_\u003cts\u003e.csv` | Every small-result-set anomaly and other 200-but-wrong finding |\n| `ExportDiagnostic_\u003cts\u003e.csv` | Export job lifecycle and the baseline-vs-filtered reconciliation |\n| `ScaleDiagnostic_\u003cts\u003e.csv` | Per-item assignment data (only when `$doScaleTest` is on) |\n\nThe run ends with a **verdict-first summary**: an Overall line (areas failed or warned, problem/retry\ncounts), then one `[PASS]` / `[WARN]` / `[FAIL]` line per diagnostic area with the single fact behind it,\nthen problem counts by type. The detailed tables follow under a `DETAIL` divider as drill-down.\n\n### Traceability\n\nEvery request carries a generated `client-request-id`, and Graph is asked to echo it back, so even failures\nwith no response (client timeouts, dropped connections) remain traceable. Every failed response's\n`x-ms-ags-diagnostic` is parsed for the **scale unit** that served that specific request, and recorded in the\nretry CSV. Note this identifies the Graph front-end gateway node, which varies per request by load\nbalancing. It is not your tenant's home scale unit (the Intune console's \"Tenant location\"), which is worth\nrecording separately when comparing across tenants.\n\n---\n\n## Prerequisites\n\n- **PowerShell 5.1 or 7+.** No external modules: `Invoke-RestMethod` and standard cmdlets only.\n- **An Entra ID app registration** using client-credentials auth, with these **application** permissions\n  granted admin consent:\n\n| Permission | Why |\n|---|---|\n| `DeviceManagementApps.Read.All` | Mobile apps and their assignments |\n| `DeviceManagementConfiguration.Read.All` | Platform scripts, compliance policies, export reports |\n| `DeviceManagementManagedDevices.Read.All` | Proactive remediations, device run states, device reports |\n\n\u003e If you already run BI for Intune, its existing app registration typically has these and can be reused.\n\u003e Check with your administrator first.\n\n---\n\n## Running it\n\n`TenantId` and `ClientId` have in-file defaults and can be overridden per run. The secret is never stored in\nthe script:\n\n```powershell\n# Prompted for the secret (hidden input)\n.\\\"Graph API Error Finder.ps1\" -TenantId \u003ctenant-guid\u003e -ClientId \u003capp-id\u003e\n\n# Or from the environment\n$env:GRAPH_DIAG_SECRET = 'paste-secret-here'\n.\\\"Graph API Error Finder.ps1\"\n\n# Or passed explicitly (avoid: it lands in your shell history)\n.\\\"Graph API Error Finder.ps1\" -ClientSecret '\u003csecret\u003e'\n```\n\nRuntime depends heavily on which diagnostics are enabled and on tenant size. The paged passes are minutes.\nThe export comparison submits one job per distinct key value and can run considerably longer, which is why\nexport jobs are batched (see `$ExportJobConcurrency`).\n\n---\n\n## Configuration\n\nSet these in the **Configuration** region near the top of the script.\n\n### Core\n\n| Setting | Default | Purpose |\n|---|---|---|\n| `$PageSize` | `50` | Default `$top` for paged endpoints |\n| `$MaxRetries` | `3` | Per-page retry ceiling |\n| `$TimeoutSec` | `60` | Per-request timeout |\n| `$LogDir` | `C:\\Temp` | Where the log and CSV artifacts are written |\n\n### Adaptive page size\n\n| Setting | Default | Purpose |\n|---|---|---|\n| `$ReduceTopOnTimeout` | `$true` | Halve `$top` on timeout-class failures |\n| `$MinTop` | `5` | Floor for reduction |\n| `$AdaptiveStickyThreshold` | `3` | Consecutive reduced pages before riding at the reduced size. `0` = pure evidence mode (retry every page at full size) |\n| `$AdaptiveProbeInterval` | `0` | `0` = never retry full size once it is known to fail. Set to e.g. `20` to periodically probe and recover if the service heals mid-run |\n\n### Diagnostic toggles\n\n| Setting | Default | Purpose |\n|---|---|---|\n| `$doVariantTest` | `$True` | Re-collect variant `$expand` shapes to isolate payload composition from page size |\n| `$doExportTest` | `$True` | Exercise the reports Export API |\n| `$doExportComparison` | `$True` | Unfiltered baseline vs per-ID filtered exports |\n| `$doRemediationTest` | `$True` | Remediation run-state three-witness comparison |\n| `$doScaleTest` | `$False` | Per-item assignment diagnostic (slow) |\n\n### Export and comparison\n\n| Setting | Default | Purpose |\n|---|---|---|\n| `$ExportJobConcurrency` | `5` | Export jobs in flight at once. Raise cautiously: export job creation is tenant-throttled |\n| `$ExportPollIntervalSec` | `10` | Seconds between job status polls |\n| `$ExportPollTimeoutSec` | `900` | Give up and flag `ExportJobStuck` |\n| `$ComparisonReport` | `DevicePolicySettingsComplianceReportV3` | Report the comparison runs against |\n| `$ComparisonKeyColumn` | `PolicyId` | Column the comparison keys on. Must match production's filter |\n| `$ComparisonMaxSettings` | `0` | `0` = every distinct key value; `N` = top-N by row count (quick runs) |\n| `$ComparisonBracketBaseline` | `$True` | Second baseline after the filtered jobs, so drift is not misreported as data loss |\n| `$ComparisonRetestMismatches` | `$True` | Re-run each mismatched filtered job once |\n\n### Remediation\n\n| Setting | Default | Purpose |\n|---|---|---|\n| `$RemediationSampleCount` | `5` | Scripts to test, ranked by device count. `0` = all (expect hours) |\n| `$RemediationPagedPageCap` | `400` | Max paged pages per script (~40k rows) |\n| `$RemediationPagedTop` | `100` | `$top` for paged run states. Note the Intune console itself pages this data at 40 |\n\n---\n\n## What to send Microsoft Support\n\nAttach `GraphDiag_\u003cts\u003e.log` plus the CSV artifacts. They are already in the shape a support engineer wants:\n\n- The verdict summary states what failed and why, in one line per area.\n- `RetryEvents.csv` gives every failure with status, timing, request id, and serving scale unit.\n- `Problems.csv` is the concrete evidence of 200-but-incomplete responses: endpoint, page, exact URL,\n  expected rows, actual rows, and the `nextLink` Graph returned alongside the short page.\n- `ExportDiagnostic.csv` shows the baseline-vs-filtered reconciliation, including which mismatches are\n  deterministic and which are drift.\n\nThe log contains your tenant ID and app registration object ID, which is normally fine for a support case.\nIt does not log secrets. Scan before sharing if you have local policy about it.\n\n---\n\n## Notes on behavior worth knowing\n\n- **Read-only.** Only `GET` requests against Graph, plus `POST` to create export jobs (which create a report\n  export, not a tenant change). Nothing in your tenant is modified.\n- **Console QuickEdit is disabled at startup** (Windows only). Selecting text in a console window blocks\n  writes and will freeze a long run. The script turns QuickEdit off for the session, and writes to the log\n  file before the console, so if output ever blocks again the log still advances with true progress. If the\n  file is ahead of the screen, the console is frozen, not the script.\n- **Paged passes run sequentially by design.** Their product is timing and failure-rate measurement, so\n  running them concurrently would contaminate the numbers. Only export jobs are batched.\n\n---\n\n## Security\n\n- Never commit a client secret. Prefer the prompt or `$env:GRAPH_DIAG_SECRET` over `-ClientSecret`.\n- Rotate the secret after the support case closes. Treat any secret pasted into a file as compromised.\n- Keep the app registration read-only, and remove it when you are done diagnosing.\n- Network access is limited to `login.microsoftonline.com` and `graph.microsoft.com`.\n\n---\n\n## License\n\n[PolyForm Shield 1.0.0](LICENSE). Copyright (c) 2024-2026 PowerStacks Corporation.\n\nAny purpose is permitted, including commercial use inside your own business, with one exception: you may\nnot use it to provide a product that competes with PowerStacks. Run it against your tenants, modify it,\nand share it freely.\n\n## Maintainer\n\nMaintained by **PowerStacks**. Issues and pull requests welcome on the [issue tracker](../../issues).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpowerstacks-corp%2Fgraph-error-finder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpowerstacks-corp%2Fgraph-error-finder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpowerstacks-corp%2Fgraph-error-finder/lists"}