v1.2.0 — BitLocker, Windows LAPS, and deprecated-settings checks for Intune Anomalies#7
Merged
Merged
Conversation
…ow + deprecated settings, consent repair - Add Invoke-RKGraphBatch private helper (Graph $batch wrapper with chunking, 429/5xx retry, id correlation) - Refactor Get-AllDeviceData compliance fetch into two batched prefetch passes - Add BitLocker key escrow + Windows LAPS backup anomaly detection (assignment-aware, filter-aware, exclude-group-aware) - Add Deprecated Settings detection with cached Get-RKIntuneSettingsCatalog helper - Fold "Not Encrypted" tab into BitLocker tab (unencrypted devices surface there) - Add Connect-RKGraph auto-repair flow for missing scopes (Invoke-RKConsentGrantRepair + Clear-RKMsalTokenCache) - Inject template-wide search bar into .rk-filter-bar via initRKTable - Add verbose timing instrumentation across Get-IntuneAnomaliesReport and Get-AllDeviceData - Fix broken JS in rendered template (missing backtick escape in select.append here-string) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…licy discovery
Replace configurationPolicies?$expand=settings (Graph hard-caps page size with
$expand, observed ~1-2 policies per cosmos page) with:
1. Bare list: GET configurationPolicies?$top=999 (small payload, 1-3 pages)
2. Per-policy GET /settings via Invoke-RKGraphBatch (sub-requests parallelize
server-side, ~75 policies finish in 4 batches of 20)
Measured on a 75-policy / 2-device tenant: configurationPolicies discovery
dropped from ~9.9s to ~1.3s; the full BitLocker/LAPS context phase from
~13.0s to ~4.2s; total Get-IntuneAnomaliesReport from 17.8s to 9.2s (-48%).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve-IntuneLapsAnomalies's secondary join enumerated all LAPS credentials for every device whose primary azureAdDeviceId lookup failed. At 1000 devices x 1000 LAPS entries that's up to 1M iterations per report. Build LapsCredentialByDeviceName at index time and use a hashtable lookup instead. Same data, same join semantics, same caller-visible behavior - just not quadratic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… app lookup Get-ApplicationFailures had two scaling bugs: 1. Per-row $apps | Where-Object scans (twice per failed row). 2. One sequential ?$expand=assignments GET per failed app. Pre-build an id->app hashtable for O(1) joins, collect the unique application ids that need an expand, and issue them as a single $batch (chunked to 20 by the helper). Behaviorally identical; same assignment-status logic, same shape. Projected wall-clock saving at 50 failing apps: ~10s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…/LAPS context
Group device-member + user-member resolution previously issued 2 sequential
v1.0/groups/{id}/transitiveMembers GETs per referenced group. At a tenant with
20 BL/LAPS-targeted groups that's 40 sequential round trips.
Pre-allocate the membership HashSets so a failed sub-request can never leave
a referenced group missing, batch all (group, devices|users) requests via
Invoke-RKGraphBatch, then fall back to sequential pagination only for groups
whose first page returned an @odata.nextLink (rare: >999 members).
Behavior preserved on small tenants (verified locally); scaling win lands on
tenants with many BL/LAPS-targeted groups.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…at tile a unique color BitLocker and LAPS tabs drop Manufacturer, Model, and Policy Assigned columns: - Manufacturer/Model are device-inventory data, irrelevant to the security anomaly each row describes (other tabs that ARE device-inventory keep them). - Policy Assigned is redundant with Applied Policies - if anything is shown there, the device is in scope. Stat tiles: rotate BITLOCKER -> t-steel (reuses already-defined unused token), LAPS BACKUP -> t-indigo (new), DEPRECATED SETTINGS -> t-pink (new). All ten tiles now use distinct colors. Light/dark theme parity preserved for the two new tokens (background, border, eyebrow, caption all defined). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…voke-RKSolutionsWithConnection) Get-GroupActivationDetails in EntraAdminRoles.ps1 was defined but never called. Invoke-RKSolutionsWithConnection (whole file + load-order entry) was a wrapper that no public cmdlet uses - report cmdlets call Connect-ToMgGraph directly via Connect-RKGraph today. ~115 lines removed. Module imports unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…APS work Captures the non-obvious patterns the codebase can't tell future-readers on its own: the Graph \$expand page-size cap, the \$batch helper's pagination caveat, the two O(N^2) accidental traps that bit this session, the module load-order constraint between IntuneAnomalies and IntuneEnrollmentFlows, and the \$script:AllGroups cache dependency between the two reports. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lver columns
Six new tests, all CI-safe (no Microsoft Graph connection required):
- Public report cmdlet shape (3 tests):
* Every report has -ExportPath or -OutputPath (caller can direct output).
* Every report uses [CmdletBinding()] so -Verbose flows.
* Every report file source-grep'd for Get-MgContext (connection guard).
- BitLocker / LAPS anomaly resolver column contracts (2 tests):
Calls Resolve-IntuneBitLockerAnomalies / Resolve-IntuneLapsAnomalies
with synthetic in-memory context + device, asserts the exact column
set + order. Would have caught today's column-trim regression
(Manufacturer/Model/PolicyAssigned removal) at the data layer instead
of by eyeballing the rendered HTML.
- Connect-RKGraph default scopes ↔ docs/PERMISSIONS.md (1 test):
Scope drift detector. Every scope literal in Connect-RKGraph.ps1
must appear in docs/PERMISSIONS.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CHANGELOG.md: expand the [Unreleased] section with the perf wins (measured 19.0s -> 8.2s on dev tenant), the column-trim + unique-tile-colors UI work, the O(N*M) fixes, dead-code removal, and the new smoke tests. Document the new -ShowExcludedDevices switch and the Connect-RKGraph auto-repair flow. - docs/CMDLET-REFERENCE.md: Get-IntuneAnomaliesReport now documents -ShowExcludedDevices and its description reflects the actual report scope (BitLocker / LAPS / deprecated settings, not just "anomalies"). - FINDINGS.md moved from repo root to .claude/FINDINGS.md to match the InforcerCommunity precedent. Local-only since .claude/ is gitignored. Skill references updated to the new path. - docs/PERMISSIONS.md and README.md: no change needed - already accurate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to the previous commit (which only captured the FINDINGS.md move): - CHANGELOG.md: add Performance, Fixes, UI, and expanded Maintenance subsections for the perf wins (19.0s -> 8.2s on a 75-policy / 2-device dev tenant), the O(N*M) lookup fixes, the column-trim + unique tile colors UI work, dead code removal, and the new Pester smoke tests. Also document the new switch and the auto-repair flow under Features. - docs/CMDLET-REFERENCE.md: Get-IntuneAnomaliesReport now documents -ShowExcludedDevices and its description reflects what the report actually covers (BitLocker, LAPS, deprecated Settings Catalog, app failures, etc.). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- module/RKSolutions.psd1: 1.1.0 -> 1.2.0 (minor: new features + perf, no breaking changes to public cmdlet surface). - CHANGELOG.md: [Unreleased] cut into [1.2.0] - 2026-06-17, condensed for an Intune/Entra admin audience (mid-level technical, not symbol-level). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR bumps RKSolutions to v1.2.0 and expands Get-IntuneAnomaliesReport with new security/configuration detections (BitLocker key escrow, Windows LAPS backup, and deprecated Settings Catalog settings), while improving runtime by introducing a reusable Microsoft Graph $batch helper and replacing several quadratic lookups with indexed structures.
Changes:
- Add BitLocker + Windows LAPS anomaly discovery (assignment resolution + Entra escrow/backup verification) and deprecated Settings Catalog detection, including
-ShowExcludedDevices. - Introduce
Invoke-RKGraphBatchand apply batching to multiple hot paths (compliance detail fetch, Settings Catalog settings fetch, app assignments expansion, group transitive members). - Update report UI/template (new tabs, new stat tiles/colors, custom per-table search input) and add Pester shape/contract tests + permission docs updates.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/Consistency.Tests.ps1 | Adds AST/shape contracts for public report cmdlets and output-column contracts for BitLocker/LAPS resolvers; adds doc drift check for default scopes. |
| module/RKSolutions.psm1 | Updates private script load order; adds new helpers (Invoke-RKGraphBatch, Get-RKIntuneSettingsCatalog). |
| module/RKSolutions.psd1 | Bumps module version to 1.2.0 and enforces PowerShell 7.0+. |
| module/Public/Get-IntuneAnomaliesReport.ps1 | Wires in new detections + -ShowExcludedDevices; adds phase timing verbose telemetry; removes standalone “Not Encrypted” tab generation. |
| module/Public/Connect-RKGraph.ps1 | Adds two new default Graph scopes + -SkipAutoRepair; ensures default RequiredScopes are forwarded to the underlying connector. |
| module/Private/Invoke-RKSolutionsWithConnection.ps1 | Removed legacy connection wrapper function. |
| module/Private/Invoke-RKGraphBatch.ps1 | New private Graph $batch helper with retry logic and correlation by request id. |
| module/Private/IntuneAnomalies.ps1 | Major changes: new HTML panels/tables/filters for BitLocker/LAPS/Deprecated Settings; new batching in device compliance resolution + app failures; adds BitLocker/LAPS/deprecation resolver implementations. |
| module/Private/Get-RKSolutionsReportTemplate.ps1 | Updates DataTables layout and injects a custom per-table search input; adds new stat tile color variants. |
| module/Private/Get-RKIntuneSettingsCatalog.ps1 | New cached downloader/loader for Settings Catalog definition metadata from GitHub releases. |
| module/Private/EntraAdminRoles.ps1 | Removes unused Get-GroupActivationDetails helper. |
| module/Private/Connect-ToMgGraph.ps1 | Adds scope auto-repair flow (consent grant patch + token cache wipe) and -SkipAutoRepair. |
| docs/PERMISSIONS.md | Documents new required scopes and updates the default scope union list. |
| docs/CMDLET-REFERENCE.md | Updates Get-IntuneAnomaliesReport documentation (new tabs + parameters + examples). |
| CHANGELOG.md | Adds v1.2.0 release notes for new detections, batching, permissions, and UI polish. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+187
to
+190
| $actualColumns = @($row.PSObject.Properties.Name) | ||
| $expectedColumns = @('Customer','DeviceName','PrimaryUser','Serialnumber','IsEncrypted','AppliedPolicies','KeyEscrowed','Status','Severity') | ||
| $actualColumns | Should -Be $expectedColumns -Because 'BitLocker anomaly rows must match the HTML template <td> order' | ||
| } |
Comment on lines
+238
to
+241
| $actualColumns = @($row.PSObject.Properties.Name) | ||
| $expectedColumns = @('Customer','DeviceName','PrimaryUser','Serialnumber','OwnerType','AppliedPolicies','LastBackupDateTime','BackupAgeDays','Status','Severity') | ||
| $actualColumns | Should -Be $expectedColumns -Because 'LAPS anomaly rows must match the HTML template <td> order' | ||
| } |
Comment on lines
+54
to
+57
| $ProgressPreference = 'SilentlyContinue' | ||
| $tmpFile = "$cacheFile.tmp" | ||
| Invoke-WebRequest -Uri $sourceUrl -OutFile $tmpFile -UseBasicParsing -ErrorAction Stop | ||
| Move-Item -Path $tmpFile -Destination $cacheFile -Force |
…al devices
Resolve-IntuneLapsAnomalies read \$d.OwnerType but Get-AllDeviceData emits
DeviceOwnership. Every real device hit the LAPS tab with an empty Ownership
cell. The initial smoke test passed because it synthesized OwnerType directly
on the fake device - the test was masking the bug instead of catching it.
Fix:
- Read \$d.DeviceOwnership in Resolve-IntuneLapsAnomalies.
- Switch all three resolver output literals to [PSCustomObject][ordered]@{}
so property iteration order is deterministic. While here, align literal
order with the HTML <td> column order (Severity before Status) - the
report rendered correctly via property-name access but Export-Csv on the
same objects would have emitted them in the wrong order.
- Update test fixture to use the real Get-AllDeviceData field name
(DeviceOwnership) AND assert the materialised value, not just the column
presence, so a future field-name drift can't sneak past the test the way
it did this time.
Fixes copilot findings #1-#5 on the PR. Finding #6 (-UseBasicParsing breaks
PS 7+) is rejected as empirically wrong: the parameter is a documented no-op
on PS 6+, verified working on PS 7.5.4, and the settings catalog has been
downloading successfully all session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds three new checks to
Get-IntuneAnomaliesReport, significantly speeds the report up via Graph$batch, and ships a backwards-compatible bump to 1.2.0.New detections
assignments across Settings Catalog, legacy device configurations, and Endpoint Security intents, honouring include / exclude groups and assignment filters.
-ShowExcludedDevicesswitch surfaces devices that were deliberately excluded from BitLocker / LAPS policies as Info-severity rows. Off by default.Performance
End-to-end report runtime on a 75-policy / 2-device tenant dropped from ~19s to ~8s. Larger tenants see proportionally bigger wins.
The main lever is a new private Graph
$batchhelper used across five hot paths (compliance fetch, Settings Catalog/settings, app?$expand=assignments, grouptransitiveMembers, and the prior$expand=settingspaginated fetch). Two quadratic per-row lookups in the BitLocker/LAPS and app-failure paths were also replaced withhashtable indexes.
Permissions
Two additional Microsoft Graph scopes are required to run the BitLocker / LAPS checks:
BitlockerKey.ReadBasic.AllDeviceLocalCredential.ReadBasic.AllBoth are
ReadBasicvariants — the report confirms a recovery key or password backup exists, but never reads the actual recovery password or local admin password.Connect-RKGraphauto-detects missing scopes on existing tokens and re-grants consent for you.Fixes
Polish
Test plan
Invoke-Pester ./Tests/Consistency.Tests.ps1— 11 / 11 tests pass (6 new: report-cmdlet shape contracts, BitLocker / LAPS resolver column contracts, scope-drift checkbetween
Connect-RKGraphdefaults anddocs/PERMISSIONS.md)Import-Module ./module/RKSolutions.psd1 -Force— clean import; manifest reports1.2.0Invoke-ScriptAnalyzer -Path ./module -Severity Warning,Error— no new findings introduced by this branchGet-IntuneAnomaliesReport -Verboseagainst a real tenant — the new BitLocker, LAPS, and Deprecated Settings tabs render, all stat tiles show distinct colorsGet-IntuneAnomaliesReport -ShowExcludedDevices— Info-severity exclusion rows appear for devices deliberately excluded from BitLocker / LAPS policiesConnect-RKGraphshould detect the two new*.ReadBasic.Allscopes and walk you through the consent flow