Skip to content

0.26.0: fix what the claims audit found, in code and help - #1

Merged
fadwen merged 5 commits into
mainfrom
fix/audit-claims-vs-code
Sep 29, 2026
Merged

fadwen merged 5 commits into
mainfrom
fix/audit-claims-vs-code

Conversation

@fadwen

@fadwen fadwen commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

Version 0.26.0. A claims audit of the module: every statement the README, the 25 command help pages, the about topic, the generated rule reference, the examples and the changelog make about behaviour was checked against the source and, where it needs no tenant or elevation, by running it. Statements that needed a tenant, SYSTEM or a signed-in lab account were checked against the dev tenant and the lab device. This pull request fixes what did not hold, in code where the code was wrong and in the documentation where the text was, with a regression test for each code fix.

No command changes shape. Every fix restores behaviour the documentation already promised.

Code fixes

Area Defect Fix
Test-IntuneDeployedScript, Compare-IntuneDeployedScript, Get-IntuneScriptHealth -Settings never reached the analysis or the settings comparison. A body-level $settings hashtable overwrote the parameter (variable names are case-insensitive), and $PSBoundParameters was tested inside a nested function, where it is that function's own. Locals renamed; the check captured before the nested functions are defined.
The same three commands -Id without -Name selected every policy, because -Name defaults to * and selection was name or id. -Id alone selects by id.
Repair-IntuneScript, Test-IntuneScript A folder path was enumerated through ForEach-Object -MemberName, which honours -WhatIf; Repair -Path <folder> -WhatIf returned nothing. Script-block enumeration.
Repair-IntuneScript, IslEncodingIssue A BOM-less file that was not UTF-8 was decoded as UTF-8 and written back with every non-ASCII character replaced by U+FFFD. Invalid UTF-8 is read in the system ANSI code page; the rule reports such a file as ANSI (Information) with the same fix.
Test-IntuneScript A type declared in a directive earned the assumed-context note and was called a portal default; a settings file's ExcludeRule beat an explicit -IncludeRule; -EnforceSignatureCheck:$false was not explicit. A directive is a declaration; an explicit include sets the file's exclusions aside; a bound switch is explicit either way.
Should-PassIntuneAnalysis A pipeline of files was joined into one path. Every file is analyzed and the failing ones are named.
Export-IntuneFindingSarif Every rule carried level warning; a finding outside -Root was written as an escaped relative URI under the root; a relative -Path was resolved against the process directory (as was Get-IntuneScriptHealth -MarkdownPath). Level of the rule's most severe finding; absolute file URI; paths resolved against the PowerShell location.
Runtime harness A missing script path returned a result with exit code -196608 instead of an error. Terminating error.
Runtime harness, -Credential A stored-password task for an account without the "Log on as a batch job" right is not always refused with 0x80070569; on the lab device it sits Ready with 0x00041303 ("has not run yet") and no error, and the launcher waited out the whole timeout. Five seconds in that state after Start-ScheduledTask is reported as the refusal, with the same hint.
Get-IntuneAgentTimeline -Id returned every timeline whose lines mentioned the id (a relationship report names two apps); a relationship report was never an outcome. Timelines filtered by their own id; the report added to the outcomes.
Compare-IntuneDeployedScript Content was compared without regard to case. Case-sensitive comparison.
Test-IntuneDeployedScript A user-context Win32 app assigned to All devices was not flagged. All devices is a device target.
Get-IntuneScriptHealth -SkipAnalysis called an unassigned policy Healthy, because the check read a finding. Read from the assignments.
Rules IslFilterIssue called -ne/-notIn with an unreported value "never matches"; IslInteractiveCall took a bare -Confirm as silencing; IslOutputIssue took $ErrorActionPreference = 'Stop' as a guard; IslPowerShell7Syntax reported using module <missing> as syntax and listed two parameters that exist on neither host; IslScriptSize called Win32 scripts remediations. Each corrected; Win32 scripts are named with the assumed limits stated.
Smaller A message typo; the Duration column dropped days; the workflow template did not split SCRIPT_PATHS in its runtime job; two empty files under docs/; the rule reference replaced literal $PSScriptRoot with <value>. Fixed.

Documentation fixes

  • Graph scopes as the Graph reference lists them: DeviceManagementScripts.Read.All for remediations and platform scripts (two examples omitted it), DeviceManagementConfiguration.Read.All for assignment filters, DeviceManagementApps.Read.All for apps, GroupMember.ReadBasic.All for the member read. Creating an export job is listed as a write, so Get-IntuneScriptHealth names the ReadWrite scopes instead of DeviceManagementManagedDevices.Read.All, in the help and in its warning.
  • Result properties (RunAs on every harness result, IntuneError as the stderr tail, SignatureStatus always present), the complete status lists, the events Get-IntuneAgentLog names and its Id rule, what -SkipRegistry leaves out, the ARM64 note on the x64 default, the base requirements Test-IntuneWin32Requirement covers, name matching and NotInTenant semantics, the Attention rules, Applied under -WhatIf, the SARIF rule level, the assignment-filter examples' full output, the file-name inference rules, the settings precedence.
  • The manifest description names the whole module. The README's links into the repository are absolute, since the README ships in the package and docs/ and Validation/ do not.
  • CLAUDE.md records the parameter-shadowing invariant. Validation/Findings.md records the 2026-09-29 re-check of the stored-password path.

Verification

  • Unit and integration suites: 869 pass on PowerShell 7.6 (7 skipped: elevation, lab credential), 812 on Windows PowerShell 5.1. PSScriptAnalyzer and the 115-character gate are clean over every changed file. Help Markdown validated, MAML rebuilt and committed, docs/Rules.md regenerated. Build/Publish-Module.ps1 -WhatIf stages and verifies 0.26.0.
  • Dev tenant, app-only session: Test-IntuneDeployedScript over 106 policies in 106 s; Compare-IntuneDeployedScript in 2 s; Get-IntuneScriptHealth over 16 policies in 24 s with the app install export at 11-17 s.
  • Lab device (Entra joined, agent enrolled), running the branch module as SYSTEM: -Context System runs in session 0 as NT AUTHORITY\SYSTEM with the system profile; -Credential for an account holding a console session runs interactively in that session (RunAs (Interactive)); the stored-password path for the same account without the batch logon right is refused and reported in 11 s; no scheduled task and no run folder remain afterwards.
  • Reporting lag measured against Graph on the lab devices: a remediation's changed result (a forced fix) reached Graph 65 minutes after the run, with the agent's next hourly report batch, and within seconds of that upload; platform script run states 2-8 s after the device's log line; app install states 30-39 s after. An unchanged remediation result is never re-reported, so lastStateUpdateDateTime is the last change, not the last run. The device's report line carries Result 4 for a fix and for a recurrence alike; the timeline help now says so.

After merge, git tag v0.26.0 && git push origin v0.26.0 publishes.

…ownPath resolved against the wrong directory

Test-IntuneDeployedScript and Compare-IntuneDeployedScript overwrote their Settings
parameter with a body-level $settings hashtable (variable names are case-insensitive) and
then checked $PSBoundParameters inside a nested function, where it is the nested
function's own; the analysis and the settings comparison never saw -Settings. -Id without
-Name matched every policy because -Name defaults to '*'. Get-IntuneScriptHealth wrote a
relative -MarkdownPath against the process directory instead of the PowerShell location.
Each has a regression test.
Repair-IntuneScript and Test-IntuneScript enumerated a folder through ForEach-Object
-MemberName, which honours -WhatIf and returned nothing. Repair decoded a BOM-less
non-UTF-8 file as UTF-8 and wrote U+FFFD in place of every non-ASCII character; it now
reads such a file in the ANSI code page (Get-IslOemEncoding -Kind ANSI) and the encoding
rule reports it as ANSI, Information, with the same fix. A type declared in a directive is
declared, so it earns no assumed-context note. An explicit -IncludeRule sets the settings
file's exclusions aside. Should-PassIntuneAnalysis takes a pipeline of files. The SARIF
export gives a rule the level of its most severe finding, writes an absolute file URI for a
script outside -Root, and resolves a relative -Path against the PowerShell location. The
harness throws for a script that does not exist. Get-IntuneAgentTimeline -Id keeps the
timeline whose id it is and counts a relationship report as an outcome. The drift compare
is case-sensitive. A user-context app on All devices is flagged like one on a device
group. Health reads 'assigned to nobody' from the assignments, so -SkipAnalysis keeps it.
A filter value no device reports is 'matches every device' for -ne and -notIn. Only
-Confirm:$false silences a prompt; $ErrorActionPreference = 'Stop' is not a guard; a
module the parser cannot find is not PowerShell 7 syntax; two parameters that exist on
neither host left the 7-only table; the size rule names Win32 scripts and says their
limits are assumed; a message typo. The rule reference keeps literal $names. The manifest
description names the whole module; the Duration column shows days; the workflow template
splits SCRIPT_PATHS in both jobs; two stray empty files are gone. Every fix has a test.
…e code does

Every passage the claims audit flagged is corrected: what each result carries (RunAs, the
stderr tail as IntuneError, SignatureStatus always present), the complete status lists, the
events Get-IntuneAgentLog names and its Id rule, what -SkipRegistry leaves out, the ARM64
note on the x64 default, the base requirements Test-IntuneWin32Requirement covers, how names
are matched and which local files count as NotInTenant, the Attention rules, the Applied
count under -WhatIf, the SARIF rule level, the filter examples' full output, the file-name
inference rules, and the settings precedence. The README's repository links are absolute
because the README ships and docs/ and Validation/ do not. An explicit
-EnforceSignatureCheck:$false is explicit. ModuleVersion 0.26.0 with the changelog entry,
the manifest release note and the CLAUDE.md invariant on parameter shadowing.
…starts; scopes as the Graph reference lists them

Verified on the lab device as SYSTEM with the branch module: -Context System runs in
session 0 as NT AUTHORITY\SYSTEM, -Credential runs interactively in the account's own
session when it has one, the task and the run folder are removed afterwards. The
stored-password path for an account without the batch logon right no longer answers
0x80070569 there: the task sits Ready with 0x00041303 and no error, and the launcher waited
out its timeout. Five seconds of that after Start-ScheduledTask is now the refusal, with a
test and a Findings note.

The Graph reference lists creating an export job as a write, so Get-IntuneScriptHealth's
help and warning name the ReadWrite scopes instead of DeviceManagementManagedDevices.Read.All;
remediations need DeviceManagementScripts.Read.All, which two examples omitted; the group
member read needs only GroupMember.ReadBasic.All. Live against the dev tenant: pre-flight
106 policies in 106 s, health 16 policies in 24 s with the export at 11-17 s.
… as Graph shows them

A remediation's changed result reaches Graph with the agent's next hourly report batch: a
fix at 01:37:34 UTC on the lab device arrived at 02:42:58 UTC, seconds after the batch
upload, and an unchanged result is never re-reported, so lastStateUpdateDateTime is the last
change rather than the last run. Platform script states arrived 2-8 s after the device's
log line, app install states 30-39 s after. The report line's Result codes, matched against
Graph on both lab devices: 3 no issue, 4 the remediation ran (fixed or recurred), 5 the
detection script failed; the timeline help had called 4 fixed and 3 failed. Findings carries
the measurement.
@fadwen
fadwen merged commit a348e9e into main Sep 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant