release: v0.6.0 — Get-InforcerSecureScore + audit event filter + dynamic completer#38
Conversation
…ffFormatted mention - Adds `Required API scope(s):` line to the .SYNOPSIS block of 13 cmdlets that previously lacked it, matching the pattern already used by the Reports cmdlets (Get-InforcerReportRun, Get-InforcerReportType, Invoke-InforcerReport, Save-InforcerReportOutput). `Get-Help <cmdlet>` now surfaces the scope requirement without users needing to open docs/API-REFERENCE.md. - Removes the stale `PolicyDiffFormatted` reference in docs/CMDLET-REFERENCE.md that survived when the property itself was deleted from the module (FINDINGS #63). - 3 cmdlets intentionally excluded from scope lines: Connect-Inforcer, Disconnect-Inforcer, Test-InforcerConnection (no scope-gated API call). Scope values sourced from the existing Cmdlet → Endpoints → Required Scopes table in docs/API-REFERENCE.md. 159/159 Pester tests pass.
… alias
New cmdlet Get-InforcerSecureScore
- Wraps GET /beta/tenants/{tenantId}/secureScores — 90-day score history,
per-category breakdown, and actionable control profiles with remediation.
- Accepts numeric ID, GUID, or tenant name via -TenantId.
- Supports -OutputType JsonObject.
- PSTypeName InforcerCommunity.SecureScore with a ListControl view showing
current/max/percentage plus counts of nested arrays.
- Aliases: CurrentScore, CurrentScorePercentage, MaxScore, LicensedUserCount,
EnabledServices, Scores, ControlProfiles, ControlCategoryScores. Nested
arrays (Scores, ControlProfiles) get their own PascalCase aliases.
Get-InforcerAuditEvent -User
- New parameter that server-side filters audit events by the specified user.
- Sends the value as the `user` field in the POST /beta/auditEvents/search
body. Previously users had to fetch all events and filter client-side.
AuditEvent.Id alias
- AuditEvent objects now expose the raw `id` field as PascalCase `Id`,
consistent with every other object type in the module.
Docs / plumbing
- README, CMDLET-REFERENCE, and API-REFERENCE updated (endpoint section,
three new schemas, cmdlet-to-scope table row, TOC).
- CHANGELOG entry for 0.6.0.
- Module version bumped 0.5.0 → 0.6.0.
- Consistency tests: expected count 20 → 21, new not-connected test, new
SecureScore and AuditEvent alias tests, updated Get-InforcerAuditEvent
param list.
- scripts/Test-AllCmdlets.ps1: new case for Get-InforcerSecureScore.
162/162 Pester tests pass.
…ication Two `Invoke-InforcerApiRequest` callers were missing `-PreserveStructure`, causing the helper's "if .data is a single object with an array property, unwrap to that array" convenience to return the wrong shape for single-object endpoints: - `Get-InforcerSecureScore`: was returning the 90-item `scores` array instead of the top-level `tenantSecureScoreDetails` object. PSTypeName was never applied. Live-verified fix: 90/90 assertions pass, aliases populated on the top-level object plus all three nested arrays. - `Get-InforcerUser -UserId`: same bug pattern — was returning one of the many nested arrays (groups, roles, devices, assignedLicenses, ...) instead of the user object. Live-verified fix. Also: - `SecureScore` aliasing now covers `controlCategoryScores` and its inner `historicScores` array (previously only top-level, `.Scores`, and `.ControlProfiles` were aliased). - `-EventType` tab completion is now dynamic — reads `$global:InforcerCachedEventTypes` (populated by `Get-InforcerSupportedEventType` at module import, refreshed on every authenticated call). New server-side event types show up automatically without a module release. Static fallback list updated to today's live 80 types (from 57). - `ReleaseNotes` in the psd1 manifest updated for 0.6.0 (was still describing 0.5.0). - CHANGELOG entry extended to cover all of the above. 162/162 Pester pass. Live-API verification against uk.inforcer.com: 85 assertions pass, 5 failures are all bugs in the verification harness (hashtable-splat mistakes), not the module.
…calhost URL is public)
There was a problem hiding this comment.
Pull request overview
This PR prepares the v0.6.0 release of the InforcerCommunity PowerShell module by adding a new Secure Score cmdlet, enhancing audit event querying and tab completion, and updating module documentation/testing to match the expanded public surface.
Changes:
- Added
Get-InforcerSecureScore(including formatting and property aliasing) and updated exports/version to 0.6.0. - Enhanced
Get-InforcerAuditEventwith-Userserver-side filtering, anIdalias, and dynamic-EventTypetab completion backed byGet-InforcerSupportedEventType. - Expanded inline required-scope help text across multiple cmdlets and refreshed docs/changelog accordingly.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| Tests/Consistency.Tests.ps1 | Updates consistency contracts and adds tests for SecureScore/AuditEvent behavior. |
| scripts/Test-AllCmdlets.ps1 | Includes the new cmdlet in the “run all cmdlets” smoke script. |
| README.md | Adds Get-InforcerSecureScore to the public cmdlet list. |
| module/Public/Invoke-InforcerAssessment.ps1 | Adds required-scope line to comment-based help. |
| module/Public/Get-InforcerUser.ps1 | Fixes -UserId detail shape by preserving API structure. |
| module/Public/Get-InforcerTenantPolicies.ps1 | Adds required-scope line to comment-based help. |
| module/Public/Get-InforcerTenant.ps1 | Adds required-scope line to comment-based help. |
| module/Public/Get-InforcerSupportedEventType.ps1 | Introduces cached event-type list used for dynamic completion and adds required-scope help text. |
| module/Public/Get-InforcerSecureScore.ps1 | New cmdlet implementation for tenant Secure Score retrieval. |
| module/Public/Get-InforcerRole.ps1 | Adds required-scope line to comment-based help. |
| module/Public/Get-InforcerGroup.ps1 | Adds required-scope line to comment-based help. |
| module/Public/Get-InforcerBaseline.ps1 | Adds required-scope line to comment-based help. |
| module/Public/Get-InforcerAuditEvent.ps1 | Adds -User filter, dynamic completer behavior, and required-scope help text. |
| module/Public/Get-InforcerAssessment.ps1 | Adds required-scope line to comment-based help. |
| module/Public/Get-InforcerAlignmentDetails.ps1 | Adds required-scope line to comment-based help. |
| module/Public/Export-InforcerTenantDocumentation.ps1 | Adds required-scope line to comment-based help. |
| module/Public/Compare-InforcerEnvironments.ps1 | Adds required-scope line to comment-based help. |
| module/Private/Add-InforcerPropertyAliases.ps1 | Adds SecureScore aliasing and adds Id alias for AuditEvent. |
| module/InforcerCommunity.psd1 | Bumps module version and exports Get-InforcerSecureScore + release notes update. |
| module/InforcerCommunity.Format.ps1xml | Adds a default list view for InforcerCommunity.SecureScore. |
| docs/openapi-snapshot.json | Updates the OpenAPI snapshot metadata. |
| docs/CMDLET-REFERENCE.md | Adds SecureScore docs and updates AuditEvent docs (+ removes stale PolicyDiffFormatted mention). |
| docs/API-REFERENCE.md | Adds Secure Score endpoint/schema documentation and route→scope mapping row. |
| CHANGELOG.md | Adds the v0.6.0 release entry. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| controlProfiles = @( | ||
| [PSCustomObject]@{ id = 'mfa-admins'; title = 'Require MFA for admins'; controlCategory = 'Identity'; currentScore = 0; maxScore = 10; scoreDifference = 10 } | ||
| ) | ||
| } | ||
| $null = Add-InforcerPropertyAliases -InputObject $secure -ObjectType SecureScore | ||
| $secure.CurrentScore | Should -Be 412.5 | ||
| $secure.MaxScore | Should -Be 640 | ||
| $secure.LicensedUserCount | Should -Be 275 | ||
| $secure.EnabledServices | Should -Be @('AzureAD','Exchange') | ||
| $secure.Scores[0].CreatedDateTime | Should -Be '2026-07-01' | ||
| $secure.Scores[0].CurrentScore | Should -Be 410 | ||
| $secure.ControlProfiles[0].Title | Should -Be 'Require MFA for admins' | ||
| $secure.ControlProfiles[0].ScoreDifference | Should -Be 10 |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
module/Public/Get-InforcerSupportedEventType.ps1:68
- The help text says this cmdlet "Requires an active Inforcer session", but the implementation returns the cached/static fallback list when not connected. This makes the help misleading for users who want to see the available tab-completion values before connecting.
.DESCRIPTION
Retrieves the valid event type names that can be used with Get-InforcerAuditEvent.
This function is primarily used for tab completion of the -EventType parameter in Get-InforcerAuditEvent.
Requires an active Inforcer session (use Connect-Inforcer to establish one).
…l one-liner
Replace 6-line BSTR marshal + ZeroFreeBSTR try/finally with
[System.Net.NetworkCredential]::new('', $s).Password.
Behavioural equivalence verified: 8 samples (empty, unicode-emoji,
newline, tab, 512-char string, standard secrets) round-trip byte-identical.
Both paths zero the unmanaged buffer — NetworkCredential uses
ZeroFreeGlobalAllocUnicode internally, same defence guarantee.
…: array Function had zero prod callers - the argument completers in Get-InforcerReportType.ps1 and Invoke-InforcerReport.ps1 read $script:InforcerReportTypeStaticKeys directly at 5 sites. Only the Pester tests called the wrapper, and they now target the array in the module scope for the same coverage. Not exported in .psd1 - no public API impact.
Get-InforcerPolicyDisplayInfo had exactly one caller (ConvertTo-InforcerDocModel) and zero direct test invocations. Merged its 114-line body into ConvertTo-InforcerDocModel.ps1 alongside its existing siblings Get-InforcerCategoryKey and Get-InforcerPolicyName, which follow the same 'helpers live in the caller file' pattern. Net: -1 private file, -31 lines. Behaviour unchanged - DocModel (66/66) and Consistency (162/162) suites pass.
…PolicyDisplayInfo PSScriptAnalyzer PSReviewUnusedParameter flagged both params - declared but never referenced in the function body. The single caller stopped passing them too. Aligns with the module's no-dead-parameters rule. DocModel 66/66 + Consistency 162/162 pass.
Pester 6 removed Assert-MockCalled (deprecated in 5). CI on Pester 6.0.0 was hitting 5 failures with 'Should operator Be is not registered' when loading the removed cmdlet. Replaced all 6 call sites with the modern Should -Invoke form (same semantics, added explicit -CommandName). Suite green on Pester 5.7.1 and 6.0.0: 397 passed, 2 skipped.
…iasing v0.6.0 CHANGELOG advertises PascalCase aliases for .ControlCategoryScores and its nested .HistoricScores, but the existing SecureScore aliasing test only covered top-level, .Scores, and .ControlProfiles. Copilot review flagged the regression risk. Extended the fixture with a controlCategoryScores entry and 4 assertions. Green on Pester 5.7.1 and 6.0.0.
Fixed issues with `Get-InforcerUser` and `Get-InforcerSecureScore` cmdlets related to API response structure. Made internal improvements including code simplifications and Pester 6 compatibility.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 27 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
module/Public/Get-InforcerSupportedEventType.ps1:68
- The help text says this cmdlet “Requires an active Inforcer session”, but the implementation intentionally returns the cached/static fallback list when not connected (to support tab completion). Updating the description avoids confusing users who run it pre-connect.
.DESCRIPTION
Retrieves the valid event type names that can be used with Get-InforcerAuditEvent.
This function is primarily used for tab completion of the -EventType parameter in Get-InforcerAuditEvent.
Requires an active Inforcer session (use Connect-Inforcer to establish one).
|
|
||
| Returns the current and historic Microsoft Secure Score for a tenant. Includes up to 90 days of daily score history, per-category scores, and actionable control profiles. | ||
|
|
||
| **Required scope**: `Tenants.SecureScores.Read` (or `Tenants.Read`) |
| <ListItem><Label>EnabledServices</Label><ScriptBlock>if ($_.enabledServices) { ($_.enabledServices) -join ', ' } else { '' }</ScriptBlock></ListItem> | ||
| <ListItem><Label>ScoreHistoryDays</Label><ScriptBlock>if ($_.scores) { @($_.scores).Count } else { 0 }</ScriptBlock></ListItem> | ||
| <ListItem><Label>ControlProfilesCount</Label><ScriptBlock>if ($_.controlProfiles) { @($_.controlProfiles).Count } else { 0 }</ScriptBlock></ListItem> |
| [System.Net.NetworkCredential]::new('', $SecureString).Password | ||
| } |
|
|
||
| Returns the current and historic Microsoft Secure Score for a tenant. Includes up to 90 days of daily score history, per-category scores, and actionable control profiles. | ||
|
|
||
| **Required scope**: `Tenants.SecureScores.Read` (or `Tenants.Read`) |
Format view improvements: - ScoreHistory now shows day count, first→last date, and latest score (was just a count). - ControlCategoryScores added to the view - previously omitted, so users saw controlCategoryScores=System.Object[] in raw dumps and got no visible per-category breakdown at a glance. - Hint field points to $obj.Scores / .ControlCategoryScores[0].HistoricScores / .ControlProfiles for the full nested data. Tests: - 3 unused $err assignments cleared (PSSA PSUseDeclaredVarsMoreThanAssignments). Two tests now assert on the error path they were meant to verify; one had no assertion at all. Suite green on Pester 5.7.1 and 6.0.0: 397 passed, 2 skipped.
Previous ScriptBlock used a regex '-replace' on what looked like a
string, but the API deserializer returns createdDateTime as a
[DateTime]. Regex missed, and the culture-formatted ToString() bled
through - nl-NL locale saw '04/09/2026 02:00:00' instead of '2026-04-09'.
Explicit .ToString('yyyy-MM-dd', InvariantCulture) fixes it. Live-verified
against tenant 14436.
Was rendering the raw double at full precision (79,153906866614).
Now formats with '{0:F2}' - display-only, underlying property is
untouched so consumers still see the full value.
Live-verified: 79,15 (culture-aware comma matches the sibling
CurrentScore field).
…ontrolProfile view Default SecureScore view now includes TopRecommendations - the 3 highest-scoreDifference controls, so the answer to 'what do I fix next' is on-screen without drilling. Each entry in .ControlProfiles now carries the InforcerCommunity.SecureScoreControlProfile PSTypeName. New ListControl view shows Title, ControlCategory, Service, current/max score with potential gain, RemediationImpact, ActionUrl, Id - dropping the giant HTML remediation blob from the default output. The full remediation HTML is still on the object as .remediation. Live-verified against tenant 14436.
Two new tests protect the drill-in patterns published in the docs: 1. 'drill-in patterns from CMDLET-REFERENCE examples' - exercises sort by ScoreDifference, filter by Id, Group-Object + Measure-Object aggregation on category totals, and .ControlCategoryScores.HistoricScores traversal. Guards against alias-pipeline regressions that would silently break the documented one-liners. 2. 'nested objects carry PSTypeName for the format view' - regression guard for the InforcerCommunity.SecureScoreControlProfile type name that the new ListControl view relies on. Without this, .ControlProfiles would render the full HTML remediation blob on every item. Also: fixed one more pre-existing PSSA PSUseDeclaredVarsMoreThanAssignments warning at line 1555 - test had a parent-scope $err that never got written to because -ErrorVariable in a child scope stays isolated. Now captures correctly and asserts the API 'Forbidden' message is extracted. Suite: 399 passed, 2 skipped on Pester 5.7.1 and 6.0.0.
- docs/CMDLET-REFERENCE.md: replaced 4 lightweight examples with 10 drill-in patterns (group-by-category with TotalGain, category-history drill-in, CSV export, Start-Process on ActionUrl, etc.). Updated the Example Output block to match the new default view (ScoreHistory date range, ControlCategoryScores inline, TopRecommendations, Hint). - docs/API-REFERENCE.md: added PSTypeName note for InforcerCommunity.SecureScoreControlProfile and explained that HTML remediation is excluded from the view but stays on the object. - Get-InforcerSecureScore.ps1: added two new .EXAMPLE blocks so Get-Help now surfaces the group-by-category and category-history drill-in patterns that the docs advertise. - .gitignore: exclude .inforcer-key.local (local live-testing key file). Suite: 399/399 on Pester 5.7.1 and 6.0.0.
…l-in docs - Expanded the SecureScore feature bullet: enumerates every field in the default ListControl view including TopRecommendations, culture-safe ISO date range, per-category inline breakdown, and 2-decimal-capped percentage. - Added a new bullet for the nested PSTypeName 'InforcerCommunity.SecureScoreControlProfile' so users know why $s.ControlProfiles | Format-List renders a compact view instead of dumping the giant HTML remediation blob. - Added a Documentation bullet cross-referencing the 10 drill-in patterns now surfaced in both CMDLET-REFERENCE and Get-Help.
Version bump 0.5.0 → 0.6.0.
What's new
🆕
Get-InforcerSecureScoreRetrieves the Microsoft Secure Score for a tenant, including:
Accepts numeric tenant ID, GUID, or friendly name. Supports
-OutputType JsonObject. RequiresTenants.SecureScores.Read(+Tenants.Readwhen using GUID/name).🔎
Get-InforcerAuditEvent -User <string>New parameter for server-side filtering. Previously you had to pull everything and filter locally:
💡
-EventTypetab completion is now dynamicTab-completing
-EventTypeused to be limited to a hardcoded list bundled with the module. It now reflects whatever event types the API currently supports — as soon as you runGet-InforcerSupportedEventTypein a session, tab completion is up to date. New Inforcer event types no longer require a module update to be tab-completable.📖
Get-Helpnow shows required scopesGet-Help Get-InforcerXnow surfaces the required API scope inline for 13 more cmdlets. No more flipping between PowerShell and the docs to figure out permissions:17 of 20 cmdlets now show scope inline. The three that don't (
Connect-Inforcer,Disconnect-Inforcer,Test-InforcerConnection) don't have a data-scope call.Bug fixes
Get-InforcerUser -UserIdreturned the wrong shape. The cmdlet was returning one of the user's nested arrays (groups, licenses, etc.) instead of the user object itself.PSTypeNamenever applied, aliases silently no-op'd. Fixed —Get-InforcerUser -TenantId X -UserId Ynow correctly returns a singleInforcerCommunity.Userobject.Get-InforcerSecureScorenested aliasing extended to.ControlCategoryScoresand its inner.historicScoresarray so category scores get PascalCase aliases like therest of the object.
PolicyDiffFormattedmention in the tenant cmdlet documentation (the property was removed in an earlier release).Full cmdlet list
Get-InforcerSecureScoreGet-InforcerAuditEvent-Userparam,Idalias, dynamic completerGet-InforcerUser-UserIdreturning wrong shapeGet-Helpnow shows required scopeTesting
Test-ModuleManifest: valid, v0.6.0completer refreshes correctly)
Test plan for reviewer
mainlocally,Import-Module ./module -ForceGet-Command -Module InforcerCommunity | Measure-Object→ 21Get-Help Get-InforcerSecureScoreshows the new cmdlet