You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat!: OAuth client credentials and the v1 API (1.0.0) - #48
inforcer now has a versioned API (v1) with API clients that log in with a client ID and secret. 1.0.0 supports both logins: an API key keeps using the legacy beta API, and client credentials use v1. Every cmdlet works with either, and v1 responses are mapped to the module's usual property names, so most scripts run unchanged.
Highlights
Connect-Inforcer -ClientId <id> -ClientSecret <secret> -Region uk, with automatic token renewal and 429 retries that honour Retry-After.
-TenantId takes a Microsoft tenant ID (GUID) or a tenant name with both logins; numeric IDs keep working with an API key.
Invoke-InforcerAssessment shows inforcer's own compliance score with client credentials. Compare-InforcerEnvironments can compare across logins, and it collects both tenants at the same time.
v1 returns Intune policies (inforcer fixed ENG-6967, verified live: v1 = beta, 244 policies). Policy types inforcer could not read are named in a warning (failedPolicyTypes).
Fixes that API-key users get too
Piping several tenants now handles every tenant. Before, only the last one was processed.
Get-InforcerAlignmentDetails -Tag with a tag no tenant has returns nothing and warns; it used to return every row.
The settings catalog updates again, and Get-InforcerSupportedEventType returns the live list.
Correct Microsoft Learn names for 11 Conditional Access apps. The export fills "Profile type" again.
Bulk report download through Save-InforcerReportOutput works.
Security:
Downloads never overwrite files.
Exports neutralise formulas, and HTML reports escape names.
Credentials never follow a redirect.
The API key no longer ends up in $Error.
The full list and the migration steps are in CHANGELOG.md under 1.0.0.
Verification
Pester: 828 passed; 1 failure that also fails on main (SettingsCatalog integration test against the live catalog data, unrelated).
Live matrix: every cmdlet and option was run with an API key and with client credentials on the same tenants (109 cases) and compared output by output. That includes one real assessment and report run per variant. It was rerun after the review fixes. (That run predates inforcer's Intune fix; Export -Tag on v1 failed then because the tag sits on an Intune policy.)
API-key output was compared with 0.8.0 on the same account; the only differences are the fixes listed in the changelog.
Reviews: consistency contract, security, performance, docs accuracy, static-to-dynamic, and API feedback. All high and medium findings are fixed.
2026-11-05: inforcer's v1 fixes (ENG-6987 to 6992) verified live and taken in: the full v1 tenant list (no per-tenant calls), the assessment score, one policy type name. Red/blue/green review of that change; every new behaviour survives mutation testing.
…PI keys
Connect-Inforcer gains a ClientCredentials parameter set (-ClientId/-ClientSecret).
The API version follows the login: API key -> /beta, client credentials -> /v1.
v1 responses are mapped to the module's existing field names at the transport
boundary, so API-key users see no change apart from a legacy-API warning.
- Get-InforcerAuthHeader is the single place auth headers are built; v1 tokens
are requested and renewed automatically
- Invoke-InforcerApiRequest: version-neutral endpoints, v1 renames, problem+json
errors, 429 Retry-After handling, 401 token renewal, collection unwrap
- New Invoke-InforcerPagedRequest, Get-InforcerTenantList (session-bound cache)
- Resolve-InforcerTenantId accepts Microsoft tenant IDs and names on v1
- Test-InforcerConnection no longer writes part of the API key to Verbose
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… audit events
- Paging goes through Invoke-InforcerPagedRequest in both modes
- Get-InforcerGroup -Group attaches v1 members from the paged members endpoint
- Get-InforcerTenantPolicies warns when v1 returns no Intune policies
- v1 audit search without -EventType no longer sends beta-only event types
- -TenantId accepts the MsTenantId alias for pipeline binding
- fix: Get-InforcerUser -OutputType JsonObject no longer throws
- fix: Get-InforcerSupportedEventType returns the live list instead of the
built-in fallback (a ?. expression parsed as a variable named $n?)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- All report and assessment requests, including the assessment runspace, the
output download, the run probe and the TAB completer, take their auth header
from Get-InforcerAuthHeader; long polls fetch a fresh header per call
- v1 report runs post msTenantIds; outputs map msTenantId to tenantId
- Permission and scope hints name v1 API client permissions on a v1 login
- Invoke-InforcerAssessment -MultiTenant finds tenants on v1
- -TenantId accepts the MsTenantId alias
- fix: the assessment-ID completer returned nothing when -ReportType was unbound
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ThrowTerminatingError is silenced when the caller passes -ErrorAction
SilentlyContinue, so cmdlets wrapping Resolve-InforcerTenantId in try/catch
returned nothing instead of their InvalidTenantId error. A plain throw of the
ErrorRecord keeps the ErrorId and is always catchable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Get-InforcerTenant -Detailed fetches per-tenant details on v1 (no-op on
beta, whose list is already rich); -TenantId returns the detailed tenant
- Get-InforcerBaseline and Get-InforcerAlignmentDetails work on v1, including
-Tag, baseline members and the scores table
- Tenant lookups reuse the cached tenant list instead of refetching
- Format views show the Microsoft tenant ID when there is no client tenant ID
- -TenantId accepts the MsTenantId alias for pipeline binding
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The caught resolver error also lands in -ErrorVariable, as it already did for
unknown tenant names, so the cmdlet's own record is not always first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It emitted the session and then the status object, so the documented
$s = Connect-Inforcer -PassThru; Compare-InforcerEnvironments -SourceSession $s
failed with 'Cannot convert System.Object[] to Hashtable'.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…forcerEnvironments
- Documentation data loads the detailed tenant on v1 and never falls back to
another tenant when the record is missing
- Baseline filtering on v1 joins on policy ids (v1 ids are Microsoft 365 ids)
- Compare resolves each baseline owner in its own session, so mixed API-key and
client-credentials sessions work
- On v1 the HTML, Markdown, Excel and comparison reports carry a note when the
API returned no Intune policies
- Export -TenantId accepts the MsTenantId alias
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m API keys
- README: quick start for both logins, legacy notice, migration guide
- CMDLET-REFERENCE: new Connect parameter set, -Detailed, MsTenantId alias,
v1 endpoints and API client permissions per cmdlet, Intune-gap notes
- API-REFERENCE: authentication, v1 envelope, errors, paging, rate limits,
every v1 path with its beta equivalent, permission tables for both models
- docs/openapi-snapshot-v1.json: production v1 spec snapshot
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ixed it
Verified live: every dynamic group carries its rule on the list.
Removed the help, view and reference text that said it was always null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- API-key logins no longer retry on 429; only v1 honours Retry-After (0.8.0 behaviour)
- cmdlets read their own error record, so network failures show their reason again
- failed requests are stripped of auth headers and body, so no token, key or
secret remains in $Error, -ErrorVariable or Get-Error
- user and group lists stream again; Select-Object -First stops paging
- a tenant missing from the cached list triggers one refresh
- a failed v1 members call warns instead of returning an empty member list
- Connect-Inforcer -BaseUrl must use https
- help, docs and tests use synthetic ids; personal-data note added
- dead fallbacks and stale comments removed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Get-InforcerReportRun -IncludeOutputs / -Wait embedded outputs without a
runId, so the documented bulk-download pipeline failed with "RunId missing"
in both login modes (also on 0.8.0). Outputs now carry runId, set where the
outputs endpoint is read. README named the internal error id for numeric
tenant ids on v1; callers see InvalidTenantId.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Get-InforcerTenantPolicies, Get-InforcerBaseline, Get-InforcerAlignmentDetails
and Export-InforcerTenantDocumentation bind -TenantId from the pipeline but had
no process block, so `Get-InforcerTenant | <cmdlet>` only handled the last
tenant (also on 0.8.0). Invoke-InforcerAssessment collects piped tenants and
runs them as one multi-tenant assessment, the same as an -TenantId array.
A consistency test now fails for any cmdlet that takes pipeline input
without a process block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eleven app ids in the -FetchGraphData name map were labelled with the wrong
or an outdated name, e.g. 01cb2876 is Device Registration Service (not Global
Secure Access), de8bc8b5 is Graph Explorer (not Microsoft Graph Command Line
Tools), d4ebce55 is Microsoft Intune Enrollment and 4765445b is OfficeHome.
Names now match Microsoft Learn; the two ids Learn does not list are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Report downloads reduce the server's file name to a safe leaf, never
overwrite an existing file (name (n).ext) and -FileName writes directly.
- HTML reports encode tenant- and API-supplied strings for their context:
the assessment matrix no longer builds inline handlers from tenant names,
the documentation search highlight builds text nodes, the comparison
category filter builds DOM elements.
- Excel export writes '=' values as text and no automatic hyperlinks;
Markdown cells and headings encode & < >.
- Requests that carry the API key, token or client secret never follow a
redirect; a 3xx fails with a clear message.
- The settings catalog freshness check decoded the release asset as bytes,
so the cache never refreshed; it now decodes UTF-8, downloads when the
remote date is unknown, and validates size and shape before replacing it.
- Saved downloads are marked as internet files on Windows; -Open no longer
launches .xls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Get-InforcerBaseline and Get-InforcerAlignmentDetails collect piped
tenants and fetch baselines, scores and tenant details once per call;
rows no longer repeat.
- Results from piped tenants carry TenantId and -OutputType JsonObject
returns one array; calls without pipeline input are unchanged.
- v1 Role, Tenant and the alignment table return a JSON array for one item.
- Baselines are cached per session (Get-InforcerBaselineList); a v1
-TenantId GUID lookup makes only the detail call.
- Beta audit search sends one request instead of one per 10 event types
(verified live: identical results).
- The assessment run renews a v1 token once on 401, honours Retry-After on
v1 429 and never follows a redirect; CSV export neutralises formulas.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… type
- Export builds Graph maps per tenant (no carry-over between piped tenants
or calls), stops before overwriting one -OutputPath file with several
tenants, and opens each HTML file once.
- Graph sign-in requests Directory.Read.All, Policy.Read.All and
DeviceManagementConfiguration.Read.All; 401/403 are not retried.
- Conditional Access app names come from Graph (static map only as
fallback), roles from roleDefinitions (custom roles too), groups via
getByIds; Entra/SharePoint/M365 settings use inforcer's own names.
- Compare fetches source and destination in parallel.
- Beta no longer sends the policy type name; Export fills Profile type
from policyTypeId (Get-InforcerPolicyTypeName, names taken from v1
responses) until inforcer restores the field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes 30 verified documentation errors (examples that did not bind,
-OutputPath being a file for assessments, Test-InforcerConnection needing a
baselines permission, swapped beta/v1 endpoints, wrong error ids and
response shapes) and documents the behaviour from the security, pipeline
and Graph fixes: piped TenantId, download rules, redirect refusal, catalog
refresh, Graph scopes and parallel Compare.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… audit event fix
-TenantId returns a fresh record per call, as in 0.8.0, so the cached-list note was wrong.
The single audit search also returns event types the per-type search missed (apiKeyCreate, apiClientCreate).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Unresolved security and correctness issues can expose client secrets in error records, trigger account-wide assessments from empty pipelines, and select groups from incomplete search results.
Introduces the 1.0.0 dual-login model: OAuth client credentials use the v1 API, while API keys retain legacy beta access. Shared helpers normalize responses and authentication so existing cmdlets can support both APIs.
Changes:
Adds v1 authentication, response mapping, tenant resolution, and shared pagination.
Improves multi-tenant pipelines, report downloads, and parallel environment comparisons.
Hardens credential handling and exports, refreshes settings-catalog handling, and documents migration.
File
Description
Tests/GraphResolution.Tests.ps1
Replaces named-location fixture GUIDs.
Tests/DocModel.Tests.ps1
Standardizes GUID comparison fixtures.
Tests/Consistency.Tests.ps1
Updates authentication, pipeline, and download assertions.
module/Public/Test-InforcerConnection.ps1
Uses shared authenticated requests for connection checks.
module/Public/Save-InforcerReportOutput.ps1
Supports both APIs and safe filename overrides.
module/Public/Invoke-InforcerAssessment.ps1
Collects piped tenants and protects CSV values.
module/Public/Get-InforcerUser.ps1
Adds shared pagination and tenant-labelled pipeline output.
module/Public/Get-InforcerTenantPolicies.ps1
Supports v1 policies and warns about missing Intune data.
module/Public/Get-InforcerTenant.ps1
Adds v1 tenant details and session-cached lookups.
…olicy type name
inforcer released fixes for the v1 API (ENG-6987..6992), verified live on UK 2026-11-05.
- The v1 tenant list now holds every field, so Get-InforcerTenant -Detailed (never released)
is removed, and -Tag and -TenantId in Get-InforcerAlignmentDetails read tags and aligned
tenants from the list: no per-tenant or /baselines call. v1 and beta share one path.
- Invoke-InforcerAssessment shows inforcer's own score (v1 summary.score) when it is a number
from 0 to 100, else it computes Pass / (Pass + Fail); the matrix report escapes the score.
- Profile type names follow the names v1 now uses on every route (8 updated, 5 added).
- A -Tag or piped tenant that matches nothing returns no rows instead of every row; -Tag
warns only when no tenant has the tag (also a fix for API-key users).
- Docs: v1 audit events carry relId/relType and list the apiClient* types; refreshed the v1
OpenAPI snapshot.
Reviewed red/blue/green; mutations of every new behaviour are caught by the tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Group name lookup no longer picks a group when a later search page failed.
- Report-run polling waits out a v1 429 (Retry-After, at most 3 times) instead of stopping.
- An empty pipeline into Invoke-InforcerAssessment runs nothing, also with -MultiTenant.
- Compare fetches the destination again in the foreground when the background fetch reported
an error, so the error follows the caller's -ErrorAction.
- Baseline scoping counts one entry per baseline policy and only lists a policy as missing
when the tenant has it under neither its name nor any of its ids.
- v1 JSON stays an array: Get-InforcerBaseline -TenantId, Get-InforcerReportRun (also [] for
no runs; a failed list request still returns nothing) and Invoke-InforcerReport -NoWait.
Each fix has a test that fails without it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Successful empty v1 tenant responses bypass JSON serialization, causing -OutputType JsonObject to return $null instead of [] and violating the documented list-array contract.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Return an empty JSON array for successful v1 tenant lists
module/Public/Get-InforcerTenant.ps1:115
A successful v1 response with no visible tenants exits here before JSON serialization, so Get-InforcerTenant -OutputType JsonObject returns $null rather than []. This breaks the documented v1 list-array contract for clients whose tenant filters match nothing. Let successful empty v1 lists reach the JSON branch, while preserving beta behavior and returning nothing when the list request fails.
Get-InforcerTenant and the Get-InforcerAlignmentDetails table returned nothing for a successful
empty v1 response, breaking the v1 list-array contract. A failed request still returns nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because the published review omits relevant concerns about Graph scope authorization and token-refresh behavior during retries.
The v1 policies endpoint now returns Intune and Defender settings-catalog policies (verified on
UK 2026-11-05: v1 = beta, 244 policies, 81 Intune). Removed the zero-Intune warning in
Get-InforcerTenantPolicies and Get-InforcerDocData, and the report note it fed (DocModel Notes in
HTML, Markdown, Excel and the Compare report), which nothing else used. Skipped types are still
named through the failedPolicyTypes warning. Docs no longer tell users to keep an API key for Intune.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed to weigh moderate-severity risks in missing-policy detection, OAuth retry handling, and Graph scope-tag permissions.
No open issues 🎉
Review effort: Balanced
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
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.
What's New
inforcer now has a versioned API (v1) with API clients that log in with a client ID and secret. 1.0.0 supports both logins: an API key keeps using the legacy beta API, and client credentials use v1. Every cmdlet works with either, and v1 responses are mapped to the module's usual property names, so most scripts run unchanged.
Highlights
Connect-Inforcer -ClientId <id> -ClientSecret <secret> -Region uk, with automatic token renewal and429retries that honourRetry-After.-TenantIdtakes a Microsoft tenant ID (GUID) or a tenant name with both logins; numeric IDs keep working with an API key.Invoke-InforcerAssessmentshows inforcer's own compliance score with client credentials.Compare-InforcerEnvironmentscan compare across logins, and it collects both tenants at the same time.failedPolicyTypes).Fixes that API-key users get too
Get-InforcerAlignmentDetails -Tagwith a tag no tenant has returns nothing and warns; it used to return every row.Get-InforcerSupportedEventTypereturns the live list.Save-InforcerReportOutputworks.$Error.The full list and the migration steps are in CHANGELOG.md under 1.0.0.
Verification
main(SettingsCatalog integration test against the live catalog data, unrelated).Export -Tagon v1 failed then because the tag sits on an Intune policy.)Full Changelog: v0.8.0...feature/oauth-auth
🤖 Generated with Claude Code