feat: shared report template, CSA report, and security hardening#5
Merged
Conversation
New-RKSolutionsReportTemplate provides the full HTML shell (head, CSS, JS, brand elements) for all reports. Implements the editorial parchment brand with light (chalk-white) and dark (warm parchment) themes, Playfair Display/Source Serif 4/JetBrains Mono typography, breadcrumb pill, eyebrow, stat tiles, status badges, and PS prompt footer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integrates the standalone Custom Security Attributes report into the module. New public cmdlet with AttributeSet/AttributeNames/Filters parameters, private helper for Graph data fetching and HTML generation using the shared brand template. Adds CustomSecAttributeAssignment.Read.All scope to Connect-RKGraph defaults. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces ~920 lines of inline HTML/CSS/JS with a call to New-RKSolutionsReportTemplate. All report-specific content (8 stat tiles, 8 tabbed DataTables, filters) preserved as template parameters. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces ~2050 lines of inline HTML/CSS/JS with a call to New-RKSolutionsReportTemplate. Preserves shimmer animation and group-jump highlight as report-specific CustomCss. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces ~3200 lines of inline HTML/CSS/JS with a call to New-RKSolutionsReportTemplate. Also fixes duplicated function body in Invoke-M365LicenseReportCore. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…plate Both New-AssignmentOverviewHtmlReport and New-DeviceVisualizationHtmlReport now use New-RKSolutionsReportTemplate. Flow diagram and architecture CSS preserved as report-specific CustomCss. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bump version to 1.1.0, correct PowerShellVersion to 7.0 - Add Get-CustomSecurityAttributesReport to FunctionsToExport - Add CustomSecAttributeAssignment.Read.All to PERMISSIONS.md - Add new cmdlet to consistency test parameter checks - Update release notes with rebrand and new cmdlet info Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Brighter, more energetic color palette while staying on-brand: - Light: accent #c06828, gradient stat tiles, vivid badges, whiter bg - Dark: accent #f0a850, gradient tiles, higher contrast text #f0e8d8, brighter badges (#60e068 green, #f0b840 amber, #f87068 red) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Card body gets explicit var(--bg-base) background - Table and td inherit background from row instead of forcing white - Even rows use var(--bg-elevated) in light, var(--bg-deep) in dark - Override Bootstrap --bs-table-bg/striped/hover variables to transparent - DataTables wrapper backgrounds set to transparent Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tables in hidden panels (display:none) can't calculate their widths. Now calls columns.adjust() after making a tab panel visible, so Subscription Overview and Disabled Users tables match the first tab. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Security (P0): - XSS: HTML-encode all Graph data in all 5 HTML report generators - XSS: fix JS filter dropdown injection with safe jQuery DOM API - Validate $From as email/GUID in Send-EmailWithAttachment Code quality (P1): - Replace array += with List[PSObject].Add() in hot loops - Add Write-Verbose to empty catch blocks in IntuneEnrollmentFlows - Add Write-Warning when Graph paging hits 10k result cap - Fix file deletion before email confirmed in IntuneAnomaliesReport - Add null-check for undefined $mermaidDiagram variable Maintenance (P2/P3): - Sanitize tenant name in export path for IntuneAnomalies - Fix typo: OperationSystemEdtionOverview -> OperatingSystemEditionOverview - Wrap Invoke-Item in try/catch for cross-platform safety - Use temp file instead of hardcoded C:\temp path - Remove unused $Filters/$AttributeNames params - Remove dead OS detection variables in Export-Results - Initialize $emailSent before conditional block - Translate Dutch comments to English - Fix double-encoded HTML arrow entity in PIM audit logs Report improvements: - Rename New-RKSolutionsReportTemplate -> Get-RKSolutionsReportTemplate - PIM Audit Logs: fix column widths with table-layout:fixed - M365 License: replace assignment type badges with plain text
There was a problem hiding this comment.
Pull request overview
This pull request introduces a shared HTML report template for the RKSolutions reporting module, adds a new Custom Security Attributes report cmdlet, and updates several reports/utilities to use the new template and improved safety checks.
Changes:
- Added
Get-RKSolutionsReportTemplateshared HTML shell and migrated report HTML generation to use it (reducing duplicated markup/JS/CSS). - Added
Get-CustomSecurityAttributesReport(plus private data/HTML helpers) to report CSA across users, devices, and enterprise apps. - Updated Graph connection scopes/docs/tests and applied several safety/quality fixes (email sender validation, paging truncation warning, mermaid export guard, typo fix).
Reviewed changes
Copilot reviewed 15 out of 19 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/Consistency.Tests.ps1 | Adds parameter contract coverage for the new CSA cmdlet. |
| module/RKSolutions.psm1 | Loads the new shared template and CSA private helpers; exports the new public cmdlet. |
| module/RKSolutions.psd1 | Bumps version to 1.1.0, updates description/release notes, exports new cmdlet. |
| module/Public/Get-IntuneEnrollmentFlowsReport.ps1 | Avoids exporting empty Mermaid content. |
| module/Public/Get-IntuneAnomaliesReport.ps1 | Fixes OS overview variable name and avoids deleting report before confirming email sent. |
| module/Public/Get-CustomSecurityAttributesReport.ps1 | New public cmdlet wrapper for CSA report generation + optional email. |
| module/Public/Connect-RKGraph.ps1 | Adds CSA-related scopes to the default scope union. |
| module/Private/Send-EmailWithAttachment.ps1 | Adds validation for -From before constructing the Graph sendMail URI. |
| module/Private/Invoke-GraphRequestWithPaging.ps1 | Warns when paging is truncated at the 10k cap. |
| module/Private/IntuneEnrollmentFlows.ps1 | Migrates large HTML scaffolding to the shared template; updates UI/JS and diagram styling. |
| module/Private/Get-RKSolutionsReportTemplate.ps1 | New shared HTML template (branding, theme toggle, DataTables helper). |
| module/Private/Export-Results.ps1 | Removes dead OS detection logic. |
| module/Private/CustomSecurityAttributes.ps1 | New CSA data retrieval and report HTML generation using the shared template. |
| docs/superpowers/specs/2026-05-03-report-integration-rebrand-design.md | Adds design/spec documentation for rebrand + CSA integration. |
| docs/superpowers/plans/2026-05-03-report-rebrand-integration.md | Adds an implementation plan document. |
| docs/PERMISSIONS.md | Documents the new CSA permissions usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+95
to
+98
| if ($SendEmail -and (Test-Path -Path $htmlPath)) { | ||
| Remove-Item -Path $htmlPath -Force | ||
| Write-Host 'INFO: Temporary report file deleted.' -ForegroundColor Green | ||
| } |
| Write-Error "Invalid -From value '$From'. Must be an email address or user object ID." | ||
| return $false | ||
| } | ||
| $sendMailUri = "https://graph.microsoft.com/v1.0/users/$From/sendMail" |
| } while (-not $success -and $retryCount -lt $MaxRetries) | ||
| if (-not $success) { break } | ||
| if ($results.Count -gt 10000) { break } | ||
| if ($results.Count -gt 10000) { |
| if (`$.fn.DataTable && `$('#allAssignmentsTable').length > 0) { | ||
| var `$allTbl = `$('#allAssignmentsTable'); | ||
| var preCategories = [], preTargets = [], preFilters = []; | ||
| function normFilter(s) { return String(s||'').replace(/\s*\((?:Include|Exclude)$$/i, '').trim() || String(s||''); } |
| @@ -2063,8 +1897,8 @@ function Get-ArchitectureDiagramFragment { | |||
| foreach ($gd in $DeviceGroupDetails) { | |||
| $encName = [System.Net.WebUtility]::HtmlEncode($gd.DisplayName) | |||
| $encType = [System.Net.WebUtility]::HtmlEncode($gd.GroupType) | |||
| $ruleCell = if ($gd.MembershipRule) { $gd.MembershipRule } else { "-" } | |||
| $groupTableRows += "<tr><td class=`"arch-group-cell`">$encName</td><td class=`"arch-group-type`">$encType</td><td class=`"arch-group-rule`">$ruleCell</td></tr>" | |||
| $ruleCell = if ($gd.MembershipRule -and $gd.MembershipRule -ne "-") { '<code>' + $gd.MembershipRule + '</code>' } else { "-" } | |||
|
|
||
| # ===== Build Overview panel ===== | ||
| $overviewHeaders = " <th>Type</th>`n <th>Name</th>" | ||
| foreach ($s in $sets) { $overviewHeaders += "`n <th>$s</th>" } |
Comment on lines
+347
to
+349
| $uniqueVals = @($allEntitiesForSet | ForEach-Object { $_.$a } | Where-Object { $_ -and $_ -ne '-' } | Sort-Object -Unique) | ||
| $options = ($uniqueVals | ForEach-Object { "<option value=`"$([System.Net.WebUtility]::HtmlEncode($_))`">$([System.Net.WebUtility]::HtmlEncode($_))</option>" }) -join '' | ||
| $filterDropdowns += "`n <select class=`"form-select ${filterId}-filter`" style=`"max-width:180px;`"><option value=`"`">All $a</option>$options</select>" |
Comment on lines
+35
to
+36
| 'CustomSecAttributeAssignment.Read.All', | ||
| 'CustomSecAttributeDefinition.Read.All' |
Comment on lines
11
to
16
| | Permission | Used by | | ||
| |------------|---------| | ||
| | **AuditLog.Read.All** | Get-EntraAdminRolesReport, Get-M365LicenseAssignmentReport | | ||
| | **CustomSecAttributeAssignment.Read.All** | Get-CustomSecurityAttributesReport | | ||
| | **CloudLicensing.Read** | Get-M365LicenseAssignmentReport | | ||
| | **CloudPC.Read.All** | Get-IntuneEnrollmentFlowsReport, Get-IntuneAnomaliesReport | |
| LicenseUri = 'https://opensource.org/licenses/MIT' | ||
| ProjectUri = 'https://github.com/royklo/RKSolutions-Module' | ||
| ReleaseNotes = @' | ||
| 1.1.0 - Rebranded all HTML reports with Carbon Ember design (Geist/Geist Mono typography, pill-style tabs, neutral dark theme). Added Get-CustomSecurityAttributesReport with multi-entity support (users, devices, enterprise apps), dynamic attribute set discovery, and coverage metrics. Fixed XSS vulnerabilities in HTML output. Added equal-width tabs, dark mode pagination, and locked DataTable column widths. Added skuFamily for OS edition detection, Windows 11 25H2 support. Added CustomSecAttributeAssignment.Read.All and CustomSecAttributeDefinition.Read.All scopes. |
royklo
pushed a commit
that referenced
this pull request
Jun 17, 2026
…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
Get-RKSolutionsReportTemplate) with rksolutions.nl branding — all 5 reportsmigrated, eliminating ~3000 lines of duplicated HTML/CSS/JS
Get-CustomSecurityAttributesReportcmdlet — auto-discovers all attribute sets, supports users, devices,and enterprise apps (not just users)
dropdowns
What changed
New features
Get-CustomSecurityAttributesReportcmdlet with auto-discovery of attribute sets across users, devices, andservice principals
recalculation on tab switch
Security fixes (P0)
-Fromparameter as email/GUID inSend-EmailWithAttachmentCode quality (P1)
$array +=withList[PSObject].Add()in M365License and CustomSecurityAttributesWrite-Verboseto empty catch blocks in IntuneEnrollmentFlowsWrite-Warningwhen Graph paging silently caps at 10,000 results$mermaidDiagramvariable with null-checkMaintenance (P2/P3)
OperationSystemEdtionOverview→OperatingSystemEditionOverview[System.IO.Path]::GetTempFileName()instead of hardcodedC:\temp→HTML entity in PIM Audit LogsNew-RKSolutionsReportTemplate→Get-RKSolutionsReportTemplateReport UI
table-layout:fixedand constrained overflowFiles changed (15)
Private/Get-RKSolutionsReportTemplate.ps1New-*, shared template with brandingPrivate/CustomSecurityAttributes.ps1Private/EntraAdminRoles.ps1Private/IntuneAnomalies.ps1Private/IntuneEnrollmentFlows.ps1Private/M365License.ps1Private/Invoke-GraphRequestWithPaging.ps1Private/Send-EmailWithAttachment.ps1Private/Export-Results.ps1Public/Get-CustomSecurityAttributesReport.ps1Public/Get-IntuneAnomaliesReport.ps1Public/Get-IntuneEnrollmentFlowsReport.ps1Public/Connect-RKGraph.ps1RKSolutions.psd1RKSolutions.psm1Test plan
Import-Module ./module/RKSolutions.psd1 -Forceloads without errors<script>tags