nordlo.windows.gporeport 0.2.0-rc.1
No packages depend on nordlo.windows.gporeport.
Added
Windows Firewall with Advanced Security is now parsed properly. It was reaching the report through the generic extension walker, which got two things wrong. Profile settings shattered into a row per setting named after the setting alone, so the domain, private and public profiles each produced a row called
EnableFirewallwith nothing saying which profile it belonged to. And a rule came out under its wire field names --Dir,LPort,RA4,EmbedCtxt-- with a grid summary ofVersion | Action | Active, which reads identically for nearly every rule in a domain.A typed handler (
ConvertFrom-GpoFirewallSetting, dispatched on theWindowsFirewallSettingsxsi:type) now emits one row per profile and one row per rule, with fields labelled the way the WFAS console labels them, protocol numbers resolved (6→ TCP), directions and actions spelled out, and profiles normalised to a consistentDomain, Private, Publicorder. A rule's summary is what identifies it at a glance -- direction, verdict, protocol and port or program, profiles -- and a rule withActive=FALSEis marked(rule disabled), since a configured rule doing nothing must not read the same as a live one. Nothing is evaluated: addresses and ports are reported as configured, with no claim about what traffic they permit.Both rule encodings are handled.
ConvertFrom-GpoFirewallRuleStringparses theregistry.polform, the single pipe-delimitedv2.31|Action=Allow|Dir=In|...string the CSE actually stores underSoftware\Policies\Microsoft\WindowsFirewall\FirewallRules-- values that orphan permanently by design (no ADMX describes them, which is whyorphaned-settingsclasses that keyCSE-owned). Unknown fields are kept under their own name rather than dropped, and a firewall element the handler does not recognise falls back to the generic walker with a warning, so a later Windows adding to the schema is visible rather than silently mis-rendered.Firewall rules stored in
registry.polare decoded too, not just those in the report XML. The CSE stores one rule as one registry value underSoftware\Policies\Microsoft\WindowsFirewall\FirewallRules(andConSecRulesfor connection security rules), named with a GUID, with the whole rule packed into the value data. No ADMX describes them, so they orphan permanently and appeared in the orphan views as an unreadable blob next to a meaningless value name — and since nothing is missing, "open it in GPMC" was the only alternative to reading the pipe string by hand.A new private
Get-GpoDecodedValueTextrenders those values through the same rule formatter the report-XML path uses, so a rule reads identically whichever source it came from.gpo_setting_valuesgains avalue_decodedcolumn, NULL for the overwhelming majority of values, which already say what they mean. The orphan expansion grows a Rule column beside the raw value — shown only under keys where something actually decoded — andall-settingscarries the reading as a column. Dispatch is on the registry key, never on the shape of the data: a value that merely contains pipe characters is not a firewall rule, and guessing from shape alone would put confident nonsense in the report. The reading also joins the GPO search index, so a rule can be found by the name that exists only in the decoding.MUI indirect string references are resolved, with the reference kept beside the reading. A firewall rule created from a built-in template does not name itself in words: its name, description and group are pointers to string resources,
@FirewallAPI.dll,-28756. A new privateResolve-GpoIndirectStringresolves these throughSHLoadIndirectString— the same API the firewall console calls — so the grid shows Inbound rule for the Remote Desktop service to allow RDP traffic. [TCP 3389] rather than the pointer.The reference is never discarded.
gpo_other_setting_propsgains aprop_raw_valuecolumn holding the value as the GPO stores it, NULL unless the displayed value is a rendering of something else, and the setting detail modal grows a "Raw value" column — shown only when a setting actually has one — under a note saying what the reading can and cannot claim. That pairing is the point: the resource is read from the machine that ran the scan, and a target on a different Windows build or MUI language can render the same reference differently, so the readable text is this host's reading and the reference is the authoritative record. A reference to a module the scanning machine does not have stays as written rather than becoming an error. Both forms are indexed for search, so a reference pasted in from GPMC still finds its rule.Administrator comments on Administrative Templates settings are now reported. The Comment tab in the Group Policy editor writes to
Machine\comment.cmtx/User\comment.cmtx, not toregistry.pol, so a report built fromregistry.polcould not see the one field written in an administrator's own words -- usually the only record of why a setting is configured. The comment now appears as an "Administrator comment" section at the top of the setting detail modal, and as acommentcolumn on every settings grain (all-settings,settings-per-gpo,settings-all-known,gpo-settings) — so the filter box on the Settings tab matches comment text like any other column, with the match highlighted. Comments are also folded into the GPOs tab's deep-search index, which makes a GPO findable by a ticket number, a colleague's initials or a phrase that appears nowhere in Microsoft's own vocabulary.Read by a new private
Get-GpoPolicyCommentand stored in a newgpo_setting_commentstable, with the file's per-GPO namespace prefixes and string table resolved to the same<namespace>::<policyName>policy id the ADMX catalogue uses. Rows are not restricted to configured policies -- the editor keeps a comment after its setting goes back to Not Configured. A comment whose prefix the file never declared is kept with a NULLpolicy_idand a warning, rather than guessed at.The full Get-GPOReport XML is now kept for every GPO and shown in the report: a collapsed "Full report XML" block at the foot of the expanded GPO row, and the same block in each setting's detail modal beside that item's own fragment, both with a Copy XML button that puts the raw document on the clipboard for an external tool. Previously only the per-item fragment survived the scan (
gpo_other_settings.source_xml), so the whole document -- the authoritative answer whenever this tool's decoding is incomplete (recognized = 0) -- meant leaving the report for GPMC.Stored in a new
gpo_report_xmltable, gzipped as raw bytes in a BLOB and inflated in the browser withDecompressionStream. These documents compress roughly ten to one, which matters because the whole snapshot is base64-embedded into the HTML; a BLOB rather than base64 text avoids encoding the payload twice. Viewing the XML needs Chrome/Edge 80+, Firefox 113+ or Safari 16.4+; older browsers get a note pointing at Download database rather than an empty box. A GPO whose report could not be generated has no row, and its report offers no XML block at all.Jump from a setting to the GPO that defines it. The GPO name in a setting detail modal is now a link that switches to the GPOs tab with that GPO filtered and expanded, and the conflict modal's list of writing GPOs is clickable the same way -- reusing the existing
showGpoExpandedbehaviour that until now was only reachable from the ADMX file expansion.
Changed
Orphaned settings now says which orphans are worth acting on, and lets you look inside one. The report was a flat count of registry keys with no backing ADMX, which is the shape of the gap but not an answer: several Group Policy areas orphan permanently by design, and the report gave a firewall key the same weight as a vendor template someone deleted from the store. Two columns now sort that out.
likely_causeclassifies the key itself —Managed policy branch(under a Policies branch, where a missing template is the usual explanation),Tattooing key(outside those branches, so the value survives the GPO no longer applying) andCSE-owned(certificate autoenrollment and trusted roots, Windows Firewall, software restriction policies — never ADMX-backed, so nothing is missing).live_gpo_countcounts the carrying GPOs that could apply anywhere at all: an enabled link and the relevant hive switched on. Rows sort actionable-first, and the CSE-owned rows are recessed rather than tinted, being the one classification that is explicitly not a finding.In the HTML each row now expands in place to the GPOs carrying that key — with their links, hive status and a Live/Inert verdict, each clickable through to the GPOs tab — and to the actual values written under it, name, type and data. That is what separates an imported vendor baseline (expected; Import-GPO carries values but not templates) from a locally authored GPO whose template went missing, and a value name is usually recognisable enough to name the template that should be there.
Detail-modal caveats moved behind an (i) on the section heading. Notes such as "Which of these applies to a given machine or user is a matter of precedence, which this report does not resolve" were printed under every table that needed them — three lines of grey prose between the reader and the next answer, repeated four times in a modal with four sections, which is how a note worth reading once becomes wallpaper. Each is now folded into a native
<details>on its own heading: click the (i) to read it, and it stays open while you work. Affects the precedence notes on "Also configured in…" and "Written by N GPOs", plus "This GPO applies to", "Editor metadata", "What this setting does", "How this policy maps to the registry" and "Administrator comment". Empty-state lines ("Not linked anywhere", "No GPO configures a setting from this file") are answers rather than caveats and stay visible, as do the per-item warning banners.Link targets are shown as canonical names rather than distinguished names —
fixture.test/Sites/Kistainstead ofOU=Kista,OU=Sites,DC=fixture,DC=test— in the GPO row expansion,dead-linksandadmx-to-gpo. A DN reads right to left; a canonical name reads the way the OU tree is navigated. The DN is unchanged ingpo_links.som_pathand remains the key everything joins on and that the object counts were gathered against; the newou_object_counts.som_canonicalcolumn carries the readable form, and the report falls back to the DN if it is absent. GPO search matches both forms, so a DN pasted from another tool still finds its GPO.The report uses the width of the window it is given. An expanded GPO row capped its settings list at 40% of the window height and sized the table to its narrowest content, so a fifteen-setting GPO showed eight rows behind a second scrollbar nested inside the grid's own, with the Category column ellipsised next to two thirds of the screen left empty. The inner cap is now the larger of 78% of the window (to a 720px ceiling), and inner tables claim up to 1200px before they wrap. Detail modals grow to 1800px rather than 1400px, and their header facts pair up two per row on windows wider than 1180px instead of running down one column beside an empty half-panel.
The XML views are legible. The raw Get-GPOReport blocks -- an item's own fragment, a Scheduled Task's targeting text and the full report XML -- rendered at 11px, which is a squint for the one view you open precisely to read carefully. Now 13px with the line height opened to match, in a box raised from 40% to 78% of the window height (to a 640px ceiling) so the larger type does not show fewer lines than before.
Fixed
The per-GPO report XML is stored as a BLOB again, so the browser can actually decompress it.
Compress-GpoReportXmlguarded itsbyte[]return withWrite-Output -NoEnumerate, which does not hold on PS 5.1 —-NoEnumeratestopsWrite-Outputfrom unrolling the array, and the function's own output processing then unrolls it anyway. The caller got anObject[]of boxed bytes, which binds as TEXT rather than BLOB with no error anywhere along the way, and the stored column failed to inflate. Returned with the unary comma form instead.The conflict detail modal no longer calls every conflict "different values". The verdict badge counted distinct GPO GUIDs instead of distinct written values, and since a row only reaches that modal because more than one GPO writes the target, the count was always greater than one. Rows where every GPO writes the same value now show
same value in all, matching the grid's verdict.Following a GPO link from a setting modal no longer traps the report in a redirect loop. The page flickered and stopped responding to input. Routing allows one hash write per navigation -- the guard that tells the report's own
hashchangefrom a user's back/forward consumes exactly one event -- but jumping to a GPO wrote two:selectTab('gpos')wrote#gpos, thenshowGpoExpandedwrote#gpos/<guid>. The unclaimed second event read as a user navigation, re-entered routing, and wrote twice again, forever. Clicking a row on the GPOs tab was safe only by accident: the hash is already#gposthere, so the first write hits the equal-value early return and never fires. Arriving from another tab -- a setting modal's GPO link, or a Findings row drilling through -- made both writes real. Applying a route from the hash had the same defect independently, so back/forward onto a GPO deep link looped as well.Multi-step navigations now run inside a hold that collects their writes and performs only the last one, which is also the most specific. Applying a route is held wholesale, so back, forward and a pasted link resolve to the hash they started from and write nothing at all; a hash that genuinely needs normalising (an alias, an unknown view, a stale findings chip, a GUID no longer in the snapshot) is corrected with a single write.
An unresolvable security-filtering trustee shows its SID instead of
(unnamed). GPMC omits the<Name>element when it cannot resolve a trustee — a deleted account, or one from a domain the scanning host cannot reach — and the parser printed a placeholder, discarding the SID that was sitting in the same element. Five trustees across four GPOs in the reference domain read(unnamed)in "This GPO applies to", which says nothing about who the GPO applies to. The SID now stands in as the principal, so the entry can be chased down.Group Policy Preferences registry collections are no longer swallowed. Registry preferences created by the GPMC wizard nest one
<Collection>per key segment, and every Collection carries anameattribute — which made the outermost one look like a settings item. The walker claimed it, never descended, and reported a single meaningless row (itemHKEY_LOCAL_MACHINE, summary\ = ()) while silently discarding every real value beneath it. Two GPOs in the reference domain lost five values each this way.<Collection>is now always treated as a container.Folder Redirection no longer fragments.
<Folder Id="Documents">identifies itself with anIdattribute rather than thenamethe rest of the report uses, so the walker did not recognise it as an item and descended into it — turning one redirected folder into four rows (DestinationPath, the security group,GrantExclusiveRights,MoveContents), none of which is a setting.Idis now an item-identifying attribute alongsidename, and supplies the row name instead of being repeated as a property.Registry preference summaries handle empty fields. Wizard items come in shapes where hive, name, type or value are empty — a key-only item that creates the key and writes nothing, and a default-value item. Both rendered as
\SOFTWARE\...\ = (). They now read...\{GUID} (key only, no value)and...\(Default) = (empty) (REG_SZ).
Any 0.0
| Version | Downloads | Last updated |
|---|---|---|
| 0.2.0-rc.1 | 3 | 08/18/2026 |
| 0.1.0 | 3 | 07/31/2026 |
| 0.1.0-rc.3 | 18 | 07/31/2026 |