Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
93ecf50
feat(api): recognize 3 error envelopes + surface x-correlation-id
royklo Jun 28, 2026
23fcdaf
feat(connect): accept any valid API key regardless of scope
royklo Jun 28, 2026
21b2985
feat(reports): add Reports API cmdlets and supporting helpers
royklo Jun 28, 2026
fa89242
docs(reports): cmdlet/API references, changelog, handoff, feedback
royklo Jun 28, 2026
aa94aa6
test(reports): Pester coverage for new cmdlets + helpers + manual har…
royklo Jun 28, 2026
c461b2d
fix(reports): first-time-user UX gaps from live testing
royklo Jun 28, 2026
773b80a
docs(reports): session handoff for next-context continuation
royklo Jun 28, 2026
03b36ac
Enhance Inforcer module with new features and tests
royklo Jun 29, 2026
bdd84eb
chore: remove outdated session handoff and reports cmdlets documentation
royklo Jun 29, 2026
2dc9ab5
fix
royklo Jun 29, 2026
35cc008
fix: preserve raw 'tags' shape in Add-InforcerPropertyAliases and rel…
royklo Jun 30, 2026
598000c
fix: improve API key handling and enhance parameter descriptions in r…
royklo Jun 30, 2026
38b2275
feat: enhance Save-InforcerReportOutput cmdlet with additional parame…
royklo Jun 30, 2026
f6e8d7b
chore: remove obsolete smoke and assessment ID completer test scripts
royklo Jun 30, 2026
611fab0
fix: update .gitignore to clarify manual test harnesses and ensure se…
royklo Jun 30, 2026
7d57564
chore: remove Reports API feedback document
royklo Jun 30, 2026
c043a84
chore: update .gitignore to include internal API feedback files
royklo Jun 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,33 @@ jobs:
run: Install-Module -Name Pester -MinimumVersion 5.0 -Force -Scope CurrentUser
shell: pwsh

- name: Run Consistency.Tests.ps1
- name: Run all Pester test files
run: |
$result = Invoke-Pester ./Tests/Consistency.Tests.ps1 -PassThru -Output Detailed
# Previously this only ran Consistency.Tests.ps1; the other 5 files in Tests/
# (DocModel, GraphResolution, Renderers, SettingsCatalog, SettingsCatalogCache —
# ~237 tests) were never exercised in CI. Run the whole Tests/ directory.
$result = Invoke-Pester ./Tests/ -PassThru -Output Detailed
if ($result.FailedCount -gt 0) { exit 1 }
shell: pwsh

smoke-test:
name: Smoke (Test-AllCmdlets.ps1)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Run Test-AllCmdlets.ps1
# No live API session — the smoke runner only verifies cmdlets are exported,
# have help, and parameter binding works. Real-API tests require a key (manual).
run: |
Import-Module ./module/InforcerCommunity.psd1 -Force
if (Test-Path ./scripts/Test-AllCmdlets.ps1) {
pwsh -NoProfile -File ./scripts/Test-AllCmdlets.ps1
} else {
Write-Host 'No Test-AllCmdlets.ps1 — skipping.'
}
shell: pwsh

static-analysis:
name: PSScriptAnalyzer
runs-on: ubuntu-latest
Expand All @@ -35,12 +56,32 @@ jobs:
run: Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser
shell: pwsh

- name: Run PSScriptAnalyzer
- name: Run PSScriptAnalyzer on module
run: |
$results = Invoke-ScriptAnalyzer -Path ./module -Recurse -Severity Error
if ($results) {
$results | Format-Table -AutoSize
throw "PSScriptAnalyzer found $($results.Count) error(s)."
throw "PSScriptAnalyzer found $($results.Count) error(s) in ./module."
}
Write-Host 'PSScriptAnalyzer (./module): no errors found.'
shell: pwsh

- name: Run PSScriptAnalyzer on tests + scripts (advisory)
# Don't fail the build on these — but surface findings so broken test files /
# smoke runners don't silently degrade.
run: |
# Run per-path: -Path takes a single string, not an array. Comma-separation lands
# as [object[]] which the parameter doesn't bind to on Linux runners.
$results = @()
foreach ($p in @('./Tests', './scripts')) {
if (Test-Path $p) {
$results += Invoke-ScriptAnalyzer -Path $p -Recurse -Severity Error -ErrorAction SilentlyContinue
}
}
if ($results) {
$results | Format-Table -AutoSize
Write-Warning "PSScriptAnalyzer found $($results.Count) error(s) outside ./module (advisory — not failing the build)."
} else {
Write-Host 'PSScriptAnalyzer (./Tests, ./scripts): no errors.'
}
Write-Host 'PSScriptAnalyzer: no errors found.'
shell: pwsh
84 changes: 23 additions & 61 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,23 @@ TestResults/

# AI tooling and planning
.claude/
.claire/
.planning/
.superpowers/
CLAUDE.md
/docs/superpowers/

# Session handoff / brief docs (internal-only, may contain API keys, personal paths, tenant data)
SESSION-HANDOFF*.md
*-HANDOFF*.md
*-handoff*.md

# Cursor
.cursor/

# Github
/.github/copilot-instructions.md

# Agents
/AGENTS.md
/SKILLS.md

# ignore test files
/**/*.html

# Settings Catalog data (62.5 MB -- copy from IntuneSettingsCatalogViewer sibling repo)
module/data/settings.json

# Sample data
/scripts/sample-data/
/**/sample-data/
Expand All @@ -68,59 +64,25 @@ module/data/settings.json
# TODO (local dev notes)
TODO.md

# Local scratch / probe notes (never commit — may contain environment URLs or sensitive context)
/docs info.md
/SCRATCH.md
/scratch.md
NOTES.local.md

# Manual / live test harnesses — disposable, may contain DEV/UAT URLs, API keys, tenant data
# or developer-specific probes. Keep these local-only; the public repo only ships the
# automated Pester suites at Tests/*.Tests.ps1.
/Tests/Manual/

# Generated documentation output
*.xlsx
*.md.bak
api-feedback.md
api-feedback-evidence/10-compliance-script-data.json
api-feedback-evidence/10-audit-events.json
api-feedback-evidence/09-pagination-metadata.json
api-feedback-evidence/09-compliance-script.json
api-feedback-evidence/08-alignment-scores.json
api-feedback-evidence/08-alignment-scores-format.json
api-feedback-evidence/07-groups-404.json
api-feedback-evidence/06-groups-visibility.json
api-feedback-evidence/05-groups-member-upn.json
api-feedback-evidence/04-groups-search.json
api-feedback-evidence/03-tenants-single-vs-list.json
api-feedback-evidence/03-tenants-json-shape.json
api-feedback-evidence/02-tenants-single.json
api-feedback-evidence/02-tenants-list.json
api-feedback-evidence/01-tenants-single-resource.json
api-feedback-evidence/01-tenants-list.json
.planning-archive/research/SUMMARY.md
.planning-archive/research/STACK.md
.planning-archive/research/PITFALLS.md
.planning-archive/research/FEATURES.md
.planning-archive/research/ARCHITECTURE.md
.planning-archive/phases/03-public-cmdlet-and-module-integration/03-VERIFICATION.md
.planning-archive/phases/03-public-cmdlet-and-module-integration/03-RESEARCH.md
.planning-archive/phases/03-public-cmdlet-and-module-integration/03-DISCUSSION-LOG.md
.planning-archive/phases/03-public-cmdlet-and-module-integration/03-CONTEXT.md
.planning-archive/phases/03-public-cmdlet-and-module-integration/03-02-PLAN.md
.planning-archive/phases/03-public-cmdlet-and-module-integration/03-01-SUMMARY.md
.planning-archive/phases/03-public-cmdlet-and-module-integration/03-01-PLAN.md
.planning-archive/phases/02-output-format-renderers/02-VERIFICATION.md
.planning-archive/phases/02-output-format-renderers/02-RESEARCH.md
.planning-archive/phases/02-output-format-renderers/02-DISCUSSION-LOG.md
.planning-archive/phases/02-output-format-renderers/02-CONTEXT.md
.planning-archive/phases/02-output-format-renderers/02-03-SUMMARY.md
.planning-archive/phases/02-output-format-renderers/02-03-PLAN.md
.planning-archive/phases/02-output-format-renderers/02-02-SUMMARY.md
.planning-archive/phases/02-output-format-renderers/02-02-PLAN.md
.planning-archive/phases/02-output-format-renderers/02-01-SUMMARY.md
.planning-archive/phases/02-output-format-renderers/02-01-PLAN.md
.planning-archive/phases/01-data-pipeline-and-normalization/01-RESEARCH.md
.planning-archive/phases/01-data-pipeline-and-normalization/01-DISCUSSION-LOG.md
.planning-archive/phases/01-data-pipeline-and-normalization/01-CONTEXT.md
.planning-archive/phases/01-data-pipeline-and-normalization/01-02-SUMMARY.md
.planning-archive/phases/01-data-pipeline-and-normalization/01-02-PLAN.md
.planning-archive/phases/01-data-pipeline-and-normalization/01-01-SUMMARY.md
.planning-archive/phases/01-data-pipeline-and-normalization/01-01-PLAN.md
.planning-archive/STATE.md
.planning-archive/ROADMAP.md
.planning-archive/REVIEW.md
.planning-archive/REQUIREMENTS.md
.planning-archive/PROJECT.md
.planning-archive/config.json
api-feedback-evidence/
# Internal API feedback / issue reports — never published; may contain DEV URLs, tenant data,
# or call IDs from live runs.
*API-Feedback*.md
*api-feedback*.md
.planning-archive/
scripts/Test-ApiFeedbackItems.ps1
37 changes: 36 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,42 @@

All notable changes to this project will be documented in this file.

The format follows [Conventional Commits](https://www.conventionalcommits.org/) and this project adheres to [Semantic Versioning](https://semver.org/). Release notes for each version are also generated from git history by the automation pipeline using the same conventional types (feat, fix, docs, refactor, test, etc.).
The format follows [Conventional Commits](https://www.conventionalcommits.org/). Versioning deviates from strict SemVer: every shipped change (feat / fix / perf / non-breaking refactor) bumps MINOR; only breaking changes bump MAJOR; docs/tests/chore-only commits don't bump. There is intentionally no `[Unreleased]` section — every entry is dated at ship time.

## [0.5.0] - 2026-06-30

### Features

- **New cmdlet: `Get-InforcerReportType`** — lists all available report types (Active User Count, Tenant Audit Report, Copilot Adoption, etc.) from `GET /beta/reports/types`. Filters by `-Key`, `-Tag`, and `-OutputFormat`. Results cached client-side and reused by tab completion; `-Force` refetches.
- **New cmdlet: `Invoke-InforcerReport`** — queues one or more report runs via `POST /beta/reports/runs`, polls until each run is complete, and saves the outputs to disk. Key capabilities:
- **Three execution modes** — default is sync + save to the current directory (or `-OutputPath`); `-NoWait` queues and returns immediately with the run IDs; `-NoSave` polls until terminal without writing files.
- **Auto-open with `-Open`** (aliases `-Show`, `-ShowResult`) — launches each saved file with the OS default handler (HTML → browser, PDF → viewer, CSV/XLSX → spreadsheet). Extensions are allowlisted so the server can't dictate launching an executable.
- **Pipeline-bindable** — `Get-InforcerReportType -Tag Security | Invoke-InforcerReport -OutputFormat csv -TenantId 482` batches every piped report type into one POST.
- **Three-phase progress bar** — queue → poll (with elapsed time and poll count) → download → complete.
- **Friendly input** — `-TenantId` accepts numeric IDs, GUIDs, or tenant names. Unknown report types trigger one auto-refresh of the catalog before failing, so new types ship without forcing a disconnect/reconnect.
- **Required scopes** — `Reports.Read` + `Reports.Run` (+ `Tenants.Read` only when `-TenantId` is a GUID or tenant name).
- **New cmdlet: `Get-InforcerReportRun`** — lists report runs from `GET /beta/reports/runs` (server caps the result at 500 items / last 7 days). `-Wait` with `-RunId` polls until the run is complete and bypasses a ~4-minute lag between completion and list visibility. `-IncludeOutputs` embeds each run's outputs.
- **New cmdlet: `Save-InforcerReportOutput`** — downloads a specific report output to disk via `GET /beta/reports/runs/{runId}/outputs/{outputId}`. Pipeline-friendly with `Invoke-InforcerReport -NoSave` and `Get-InforcerReportRun -IncludeOutputs`. Uses the server's `Content-Disposition` filename (with full Unicode support); `-FileName` overrides. Sanitizes Windows reserved names (`CON`, `PRN`, `AUX`, etc.) and caps length at 200 characters while preserving the extension. Accepts optional `-ReportType`, `-OutputFormat`, and `-TenantId` (alias `-ClientTenantId`) pipeline-bindable pass-through parameters so the emitted result matches the `InforcerCommunity.ReportRunResult` format view when piped from upstream cmdlets.
- **Dynamic tab completion for `-AssessmentId` on `Invoke-InforcerReport`** — three permission-aware states: not connected (hint to run `Connect-Inforcer`), connected without scope (hint about `Assessments.Read`, denial cached so subsequent TABs are instant), connected with scope (friendly names in the dropdown, opaque assessment ID inserted on selection, `name — id` shown as the tooltip).
- **`Connect-Inforcer` primes the Reports catalog cache** on a successful connect (best-effort, 4-second budget, silent on missing scope) so tab completion on `Invoke-InforcerReport -ReportType` shows live keys on the very first attempt.
- **API errors now include field-level details.** When the server returns a structured `errors[]` array, each entry is rendered into the error text — `Invoke-InforcerReport` against a tenant outside the key's scope now reads *"Validation failed — tenants.includeTenants contains tenant X that is not in the API key's scope"* instead of the generic *"see errors for details"* placeholder. The `x-correlation-id` response header is also captured (visible in verbose output and included in error records) for support tickets.

### Documentation

- Sections for all four Reports cmdlets added to `docs/CMDLET-REFERENCE.md`; Reports endpoints and schemas added to `docs/API-REFERENCE.md`; the new cmdlets (plus `Get-InforcerSupportedEventType`, which was missing) added to the README public surface table.

### Bug Fixes

- **`Connect-Inforcer` now reports a meaningful error on HTTP 401 in PowerShell 7.** Previously the error handler only fired on the PowerShell 5.1 exception type, so PS7 users saw an empty error message when the API key was rejected. Now extracts the status code and message from either exception path.
- **`Connect-Inforcer` accepts any valid API key, regardless of scope.** Customers with keys scoped only to `Reports.Read`, `Assessments.Read`, or `Audit.Read` previously couldn't connect because the validation probe required `Baselines.Read` / `Tenants.Read` and a 403 was treated as failure. The cmdlet now distinguishes "key rejected" from "key valid but scope missing" and accepts the latter.
- **`-AssessmentId` dynamic completer caches denial on HTTP 401 as well as 403.** The gateway returns 401 for both subscription-level rejection and missing scopes; previously only 403 was cached, so the second TAB still hit the network. Both are now cached.
- **JSON depth 100 enforced** in three pre-existing helpers (`Get-InforcerComparisonData`, `Compare-InforcerDocModels`, `Resolve-InforcerGraphEnrichment`) and two cache-metadata writes in `Get-InforcerSettingsCatalogPath` that had no explicit depth — prevents truncation on deep policy structures.
- **`Invoke-InforcerReport -AssessmentId ''` now throws a clear local error** instead of forwarding `assessment-id=''` to the server (which produced a generic validation failure). Empty / whitespace-only values are rejected up front with a hint to run `Get-InforcerAssessment`.

### Tests

- 6 new Pester tests for the `errors[]` array renderer covering field/code/message rendering, plain-string entries, alternate property aliases, null/empty inputs, and unrecognized object shapes. Total suite: 394 pass / 0 fail / 2 legit skips.
- Pre-merge live smoke harness (`Tests/Manual/Live-ApiSmoke.ps1`, gitignored) verified all four Reports cmdlets end-to-end against the DEV API: 24/24 PASS.

## [0.4.0] - 2026-05-15

Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,18 @@ Disconnect-Inforcer
| **Get-InforcerTenantPolicies** | Retrieves policies for a specified tenant. |
| **Get-InforcerAlignmentDetails** | Retrieves alignment scores (table or raw format). |
| **Get-InforcerAuditEvent** | Retrieves audit events (optional EventType, date range; -EventType has tab completion). |
| **Get-InforcerSupportedEventType** | Returns the canonical list of audit event types accepted by `Get-InforcerAuditEvent -EventType`. Drives the tab completer; cached after first call. |
| **Get-InforcerUser** | Retrieves users from a tenant (list/search or detail by UserId). |
| **Get-InforcerGroup** | Retrieves Entra ID groups from a tenant (list/search or detail by GroupId). |
| **Get-InforcerRole** | Retrieves Entra ID directory role definitions from a tenant. |
| **Export-InforcerTenantDocumentation** | Generates comprehensive tenant documentation in HTML, Markdown, or Excel format. |
| **Compare-InforcerEnvironments** | Compares two tenants' Intune configuration and generates an interactive HTML comparison report. Supports baseline-scoped comparison via `-SourceBaselineId` / `-DestinationBaselineId` with automatic baseline owner resolution. |
| **Get-InforcerAssessment** | Lists available assessments (CIS, Essential Eight, etc.). |
| **Invoke-InforcerAssessment** | Runs an assessment against one or more tenants. Supports HTML/CSV export. |
| **Get-InforcerReportType** | Lists the report catalog from the Reports API. Cached after first call; supports `-Key`, `-Tag`, and `-OutputFormat` filters. |
| **Invoke-InforcerReport** | Queues a report run, polls until complete, and saves outputs to disk. Use `-NoWait` for async or `-NoSave` to download metadata only. |
| **Get-InforcerReportRun** | Lists report runs (last 7 days, capped at 500). Use `-Wait` with `-RunId` to bypass the 4-minute list propagation lag. |
| **Save-InforcerReportOutput** | Downloads a report output to disk using the server's `Content-Disposition` filename. Pipeline-friendly for bulk download. |


For full parameter details and example output, see **[Cmdlet Reference](docs/CMDLET-REFERENCE.md)**. For detailed API schemas and response structures, see **[API Reference](docs/API-REFERENCE.md)**.
Expand Down
Loading