Skip to content

Harness module path under PowerShell 7, and rule fixes backed by validation round 10 - #4

Merged
fadwen merged 3 commits into
mainfrom
fix/harness-module-path-and-rule-evidence
Oct 5, 2026
Merged

fadwen merged 3 commits into
mainfrom
fix/harness-module-path-and-rule-evidence

Conversation

@fadwen

@fadwen fadwen commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a runtime-harness defect that made scripts fail locally that run fine under Intune, corrects four rule behaviours, and backs each rule change with a new validation round on a lab device (round 10 in Validation/Findings.md).

No command changes shape. ModuleVersion is not bumped; the changes are under Unreleased in the changelog.

Runtime harness

A user-context run started from PowerShell 7 launched the Windows PowerShell 5.1 host with PowerShell 7's PSModulePath. The 5.1 host then loaded Microsoft.PowerShell.Management, Utility and Security from PowerShell 7's folders: no Cert: drive, and Get-AuthenticodeSignature and ConvertTo-SecureString failed to load.

The child now gets the session's path without the three folders PowerShell 7 adds for itself ($PSHOME\Modules, Program Files\PowerShell\Modules, Documents\PowerShell\Modules). This is the same reset PowerShell 7 applies when it starts powershell.exe as a command; it does not apply it to a process started through System.Diagnostics.Process, which is how the harness starts one. A folder the session added still reaches the child.

Runs started from Windows PowerShell, and the scheduled-task runs (-Context System, -Credential), are unchanged.

Rules and Repair-IntuneScript

Area Before Now
Repair-IntuneScript return 1; exit 1 became 1; exit 0; exit 1: same behaviour, the exit the author wrote unreachable, and no finding left. A script-scope return with an exit other than 0 after it in the same block carries no edit and stays reported. Other returns are fixed as before.
IslPowerShell7Syntax, 7-only cmdlets, parameters and -Parallel Cited the parse error's evidence ("the detection exits 1 and the remediation runs"). These parse under 5.1. The findings say the call fails and the script carries on, and cite REM-PS7-CMDLET, REM-PS7-PARAM and REM-PS7-PARALLEL.
IslPowerShell7Syntax, #Requires -Version 7 Same parse-error evidence. Cites REM-PS7-REQUIRES.
IslPowerShell7Syntax, Out-File -Encoding utf8NoBOM Listed in the rule's table but filtered out by the code that read it, so it never fired. An empty Rename-Item entry was also inert. A finding, citing REM-PS7-ENCODING. The empty entry is removed.
IslInteractiveCall, Get-Credential Always an Error, including Get-Credential -Credential $built, which does not prompt. Error where it is sure to prompt (bare, with -Message, -UserName or -Title, or handed a literal name). Warning when -Credential is handed anything else.
IslContextIssue, drive letters Every path from D:\ to Z:\ in a SYSTEM script was a Warning, "not mapped for SYSTEM". Information. The letter cannot say whether it is a local volume or a mapped drive, so the message says which case fails.

Validation round 10

Seven SYSTEM remediations on the Entra joined lab device (Windows PowerShell 5.1.26100.9444), read from the agent's own result records, the probe files and Graph deviceRunStates.

Experiment Observed
REM-PS7-CMDLET (Test-Json) CommandNotFoundException, the next line ran, exit 0, "without issues", no remediation
REM-PS7-PARAM (ConvertFrom-Json -AsHashtable) NamedParameterNotFound, then the same
REM-PS7-PARALLEL (ForEach-Object -Parallel) AmbiguousParameterSet, then the same
REM-PS7-ENCODING (Out-File -Encoding utf8NoBOM) ParameterArgumentValidationError, no file written, then the same
REM-PS7-REQUIRES (#Requires -Version 7.0) Exit 1 without running (ScriptRequiresUnmatchedPSVersion), the remediation ran, status Recurred, Graph fail / remediationFailed
REM-CRED-BUILT Get-Credential -Credential handed a PSCredential returned it in 12 ms, no prompt
REM-DRIVES-SYS SYSTEM listed local C: and D: and found no X:, while the signed-in user had X: mapped to a share before and after the run

Validation/New-IslDriveFixture.ps1 creates and removes the drive state REM-DRIVES-SYS reports on, so the experiment can be repeated.

One value is recorded and not explained: the REM-PS7-REQUIRES result record carries RemediationExitCode 1, although that remediation ends in exit 0 and wrote its probe record.

Documentation

  • Invoke-ScriptAnalyzer -Severity filters the custom rules on their registered severity, which PSScriptAnalyzer 1.25.0 sets to Warning for every custom rule. -Severity Error therefore returns none of the wrapper's records and -Severity Warning returns all of them. The README and the Get-IntuneAnalyzerRulePath help now say so and point to Where-Object Severity -eq Error. An integration test pins the behaviour, so a PSScriptAnalyzer release that changes it fails the test.
  • The README and help said the wrapper answers nested script blocks from a cache. It skips them; the cache serves the other rules at the root.
  • The about topic named "a missing exit" among Repair-IntuneScript's fixes. They are a script-scope return, the encoding and a padded requirement value.
  • Validation/Findings.md records the query user column layout seen on the lab device and states that a user name longer than the column is still unmeasured.
  • A backtick line continuation left in Get-IslSetting is removed.

Not changed

  • Get-IslLogonSession parses query user. Fed a hand-made line with a 21-character name, it reads the name and the session name as one field, so the account would not match and the launcher would fall back to the stored-password task. No account with a name that long was available on the lab device, so the real output is unknown and the parser is left as it is.
  • Validation/Invoke-ValidationRound.ps1 -Action Collect failed on the lab device's accumulated probe records (about 11 MB; ConvertFrom-Json stopped at record 5,637). The round's seven experiments were read directly instead. The driver is not fixed here.

Verification

  • Unit and integration suites: 899 pass on PowerShell 7.6.6 (8 skipped: elevation, lab credential). On Windows PowerShell 5.1 the unit suites and the two integration files this change touches: 865 pass (17 skipped).
  • PSScriptAnalyzer (Error and Warning) is clean in a fresh process. Help Markdown validated, MAML rebuilt and committed, docs/Rules.md regenerated. Build/Publish-Module.ps1 -WhatIf stages and verifies the package.
  • The module-path fix was measured before and after from PowerShell 7 with a probe script run through the harness: before, modules at version 7.0.0.0 and no Cert: drive; after, the 3.x modules and a working Cert: drive. The new integration test asserts the same.
  • The seven round-10 remediations remain deployed in the dev tenant with the other ISL-* objects.

fadwen added 3 commits October 5, 2026 08:39
…7's modules

A user-context run started from pwsh inherited the session's PSModulePath, so the Windows PowerShell child took Microsoft.PowerShell.Management, Utility and Security from PowerShell 7's folders: no Cert: drive, and Get-AuthenticodeSignature and ConvertTo-SecureString failed to load. The child now gets the session's path without the three folders PowerShell 7 adds for itself, the reset pwsh applies when it starts powershell.exe as a command. Runs started from Windows PowerShell and the scheduled-task runs are unchanged.
… that hid the finding

Round 10 on the joined lab device (Validation/Findings.md): a cmdlet, parameter or value only PowerShell 7 has, and ForEach-Object -Parallel, fail where they stand and the detection runs on to its own exit 0; #Requires -Version 7 exits 1 and the remediation runs; Get-Credential handed a built credential returns it without a prompt; SYSTEM sees local volumes and not the drive the signed-in user mapped.

IslPowerShell7Syntax cited the parse error's evidence for all of these; each finding now says what happens and cites its own experiment. Out-File -Encoding utf8NoBOM was in the rule's table and never matched; it is a finding. IslInteractiveCall keeps Get-Credential an error where it is sure to prompt and warns when -Credential is handed anything but a literal. IslContextIssue's drive-letter finding is Information and says which case fails.

Repair-IntuneScript turned 'return 1; exit 1' into '1; exit 0; exit 1' and left no finding. A script-scope return with an exit other than 0 after it in the same block now carries no edit.
…rks, Repair's fixes by name

PSScriptAnalyzer 1.25.0 filters -Severity on a custom rule's registered severity, Warning for every one, so -Severity Error returns none of the wrapper's records; the README and the help say so and an integration test pins it. The wrapper skips nested script blocks and its cache serves the other rules at the root. The about topic names Repair's three fixes. Changelog for the unreleased changes; a backtick continuation left in Get-IslSetting is gone.
@fadwen
fadwen merged commit c455b86 into main Oct 5, 2026
4 checks passed
@fadwen
fadwen deleted the fix/harness-module-path-and-rule-evidence branch October 5, 2026 19:31
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