From 3b8a1afb5e5c187469e3b408a323ab7b528a227e Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:33:13 -0400 Subject: [PATCH 1/2] refactor(machine-health): derive the elevation banner coverage from the catalog The banner's "Covered non-elevated" list was a hardcoded nine-line list that had drifted behind the catalog. Get-NonElevatedCoverage now derives it from the enabled needs_admin: false entries through Format-CheckLabel, which moves to its own lib file so the banner and the report label checks the same way. The banner call moves after the catalog load. The prose table in elevation-matrix.md is removed in favor of Get-ElevationMatrix.ps1. Co-Authored-By: Claude Opus 5.5 --- ...health-elevation-single-source-08179546.md | 7 ++ .../reference/windows/elevation-matrix.md | 12 +--- .../windows/Invoke-MachineHealthCheck.ps1 | 65 +++++++------------ .../scripts/windows/lib/Format-CheckLabel.ps1 | 25 +++++++ .../windows/lib/Get-NonElevatedCoverage.ps1 | 32 +++++++++ .../windows/lib/Format-CheckLabel.Tests.ps1 | 36 ++++++++++ .../lib/Get-NonElevatedCoverage.Tests.ps1 | 34 ++++++++++ .../lib/Write-ElevationBanner.Tests.ps1 | 23 ++++++- 8 files changed, 182 insertions(+), 52 deletions(-) create mode 100644 .changes/machine-health/refactor-machine-health-elevation-single-source-08179546.md create mode 100644 plugins/machine-health/skills/audit/scripts/windows/lib/Format-CheckLabel.ps1 create mode 100644 plugins/machine-health/skills/audit/scripts/windows/lib/Get-NonElevatedCoverage.ps1 create mode 100644 plugins/machine-health/skills/audit/tests/windows/lib/Format-CheckLabel.Tests.ps1 create mode 100644 plugins/machine-health/skills/audit/tests/windows/lib/Get-NonElevatedCoverage.Tests.ps1 diff --git a/.changes/machine-health/refactor-machine-health-elevation-single-source-08179546.md b/.changes/machine-health/refactor-machine-health-elevation-single-source-08179546.md new file mode 100644 index 00000000000..504d6301a16 --- /dev/null +++ b/.changes/machine-health/refactor-machine-health-elevation-single-source-08179546.md @@ -0,0 +1,7 @@ +--- +bump: patch +--- + +### Changed + +- The pre-run elevation banner's "Covered non-elevated" list now names every enabled check that runs without admin, read from the catalog, instead of a fixed list that had fallen behind; `reference/windows/elevation-matrix.md` points to `Get-ElevationMatrix.ps1` instead of keeping its own copy of the table. diff --git a/plugins/machine-health/skills/audit/reference/windows/elevation-matrix.md b/plugins/machine-health/skills/audit/reference/windows/elevation-matrix.md index 8cc0059f48f..b437423013a 100644 --- a/plugins/machine-health/skills/audit/reference/windows/elevation-matrix.md +++ b/plugins/machine-health/skills/audit/reference/windows/elevation-matrix.md @@ -1,6 +1,6 @@ # Elevation matrix for Windows -SSOT for which Windows capabilities the skill can and cannot exercise when the process is not Administrator. Structured form lives in `scripts/windows/lib/Get-ElevationMatrix.ps1`. When adding an admin-gated signal, update both files. +Which Windows capabilities the skill can and cannot exercise when the process is not Administrator, and why. ## Policy @@ -11,12 +11,7 @@ SSOT for which Windows capabilities the skill can and cannot exercise when the p ## Capabilities that require elevation -| Feature | Check | Fields populated when elevated | Reason | -|---|---|---|---| -| SMART disk reliability counters | `disk-space` | `temp_c`, `wear_pct`, `read_errors`, `write_errors` | `Get-StorageReliabilityCounter` requires admin on most drives (NVMe/SATA alike). Non-elevated returns empty counters silently. | -| Windows Update driver catalog | `drivers` | `pending_driver_updates` | PSWindowsUpdate's `Get-WindowsUpdate -Category Drivers` wraps Windows Update Agent COM API, requiring admin for elevation-sensitive calls. | -| pnputil device problem codes | `drivers` | `problem_devices` | `pnputil /enum-devices /problem` lists devices with non-zero problem codes (10/28/43/45). Admin-only for complete output. | -| TPM + BitLocker status | `tpm-bitlocker` | `tpm_owned`, `tpm_enabled`, `bitlocker_protection_status` | `Get-Tpm` + `Get-BitLockerVolume` require admin. | +`scripts/windows/lib/Get-ElevationMatrix.ps1` is the one list: each row names the feature, the check that consumes it, the fields it populates when elevated, and the reason elevation is required. The pre-run banner and the report's elevation-coverage block render from it. The banner's "Covered non-elevated" list is derived from the catalog: enabled entries with `needs_admin: false`. ## How to run elevated @@ -35,9 +30,6 @@ Or schedule the weekly task to run as `SYSTEM` / an admin account. That is out o 1. Add a check (or extend an existing one) declaring `needs_admin: $true` via `New-HealthResult -NeedsAdmin $true`. 2. When non-elevated, call `New-HealthResult -AdminFields @('fieldA', 'fieldB')` so the report's elevation-coverage block enumerates skipped fields. 3. Add a row to `Get-ElevationMatrix.ps1` with `Feature`, `CheckId`, `Fields`, `Reason`. -4. Add a row to the table above with same fields. - -Banner and report pull from `Get-ElevationMatrix.ps1`; table above is prose counterpart. Drift check: row counts should match. ## Cross-OS portability diff --git a/plugins/machine-health/skills/audit/scripts/windows/Invoke-MachineHealthCheck.ps1 b/plugins/machine-health/skills/audit/scripts/windows/Invoke-MachineHealthCheck.ps1 index bc65855f250..24014eeb0cc 100644 --- a/plugins/machine-health/skills/audit/scripts/windows/Invoke-MachineHealthCheck.ps1 +++ b/plugins/machine-health/skills/audit/scripts/windows/Invoke-MachineHealthCheck.ps1 @@ -65,6 +65,8 @@ $skillRoot = Resolve-SkillRoot . (Join-Path $libRoot 'Invoke-AllowlistedWeb.ps1') . (Join-Path $libRoot 'Get-ElevationMatrix.ps1') . (Join-Path $libRoot 'Write-ElevationBanner.ps1') +. (Join-Path $libRoot 'Format-CheckLabel.ps1') +. (Join-Path $libRoot 'Get-NonElevatedCoverage.ps1') . (Join-Path $libRoot 'ConvertTo-TopMetrics.ps1') . (Join-Path $libRoot 'Invoke-TrendAnalysis.ps1') . (Join-Path $libRoot 'Get-RunDelta.ps1') @@ -165,36 +167,6 @@ if ($effectiveDry -and -not $DryRun.IsPresent) { $elevated = Test-IsElevated -# Pre-run elevation banner (stderr only; stdout remains clean JSON). -# Non-elevated + no -SkipBanner => print the coverage matrix so the user -# knows up-front what will be degraded and how to re-run with admin. -try { - $matrix = @(Get-ElevationMatrix) - $nonElevatedCoverage = @( - 'disk space (used %, free GB, volume health)' - 'services state (Automatic services stopped)' - 'event log errors (BugCheck, Kernel-Power 41, WUClient, disk)' - 'Windows Update pending-reboot signals' - 'Defender status (signatures, RTP, tamper protection)' - 'winget upgrades (CISA KEV correlation)' - 'battery health (laptop only)' - 'driver inventory + signing + CodeIntegrity rejections' - 'reliability monitor score' - ) - Write-ElevationBanner ` - -Elevated $elevated ` - -HostName $hostname ` - -UserName ("$env:USERDOMAIN\$env:USERNAME") ` - -OutputBase $OutputBase ` - -StateBase $StateBase ` - -SkillRoot $skillRoot ` - -Matrix $matrix ` - -NonElevatedCoverage $nonElevatedCoverage ` - -Quiet:$SkipBanner -} catch { - Write-MachineHealthLog "banner_render_failed $($_.Exception.Message)" -} - # User-load heuristic: last input <60s. LASTINPUTINFO is user32 interop -- # if unavailable, default to "not loaded." function Test-UserLoad { @@ -300,6 +272,26 @@ $windowsChecks = @($validatedChecks | Where-Object { ($_.os -contains 'windows') -and ($_.enabled) -and (-not $_.deprecated) }) +# Pre-run elevation banner (stderr only; stdout remains clean JSON). +# Non-elevated + no -SkipBanner => print the coverage matrix so the user +# knows up-front what will be degraded and how to re-run with admin. The +# covered list comes from the loaded catalog, so it runs after the load; a +# failed load leaves it empty and the banner omits that section. +try { + Write-ElevationBanner ` + -Elevated $elevated ` + -HostName $hostname ` + -UserName ("$env:USERDOMAIN\$env:USERNAME") ` + -OutputBase $OutputBase ` + -StateBase $StateBase ` + -SkillRoot $skillRoot ` + -Matrix @(Get-ElevationMatrix) ` + -NonElevatedCoverage @(Get-NonElevatedCoverage -Catalog $windowsChecks -CustomIds $customIds) ` + -Quiet:$SkipBanner +} catch { + Write-MachineHealthLog "banner_render_failed $($_.Exception.Message)" +} + # Load history tail. Depth 100 (not 8) so cadence selection can still find a # monthly check's last run when many weekly reruns / on-demand runs fall inside # the ~27-day interval -- an 8-line tail would push the last actual run off the @@ -685,20 +677,13 @@ $oneLine = ("$($totalBySeverity.CRIT) CRIT, $($totalBySeverity.WARN) WARN, " + "$($totalBySeverity.INFO) INFO, $($totalBySeverity.OK) OK, " + "$($totalBySeverity.UNKNOWN) UNKNOWN") -function Format-CheckLabel { - [CmdletBinding()] - [OutputType([string])] - param([Parameter(Mandatory = $true)] [string] $Id) - return ($customIds -contains $Id) ? "$Id [custom]" : $Id -} - function Format-Finding { [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory = $true)] $Result) $lines = [System.Collections.Generic.List[string]]::new() - $lines.Add("#### $(Format-CheckLabel -Id $Result.id) - $($Result.summary)") + $lines.Add("#### $(Format-CheckLabel -Id $Result.id -CustomIds $customIds) - $($Result.summary)") $lines.Add('') $lines.Add("**Severity:** $($Result.severity)") $lines.Add('') @@ -739,11 +724,11 @@ function Format-Section { $okList = @($checkResults | Where-Object { $_.severity -eq 'OK' } | - ForEach-Object { "- **$(Format-CheckLabel -Id $_.id)** - $($_.summary)" }) -join "`n" + ForEach-Object { "- **$(Format-CheckLabel -Id $_.id -CustomIds $customIds)** - $($_.summary)" }) -join "`n" if (-not $okList) { $okList = '_none_' } $glanceRows = @($checkResults | ForEach-Object { - "| $($_.category) | $(Format-CheckLabel -Id $_.id) | **$($_.severity)** | $($_.summary) | $(Format-TrendCell -Result $_) |" + "| $($_.category) | $(Format-CheckLabel -Id $_.id -CustomIds $customIds) | **$($_.severity)** | $($_.summary) | $(Format-TrendCell -Result $_) |" }) -join "`n" $glanceTable = @" | Category | Check | Severity | Summary | Trend | diff --git a/plugins/machine-health/skills/audit/scripts/windows/lib/Format-CheckLabel.ps1 b/plugins/machine-health/skills/audit/scripts/windows/lib/Format-CheckLabel.ps1 new file mode 100644 index 00000000000..f2f98188141 --- /dev/null +++ b/plugins/machine-health/skills/audit/scripts/windows/lib/Format-CheckLabel.ps1 @@ -0,0 +1,25 @@ +#Requires -Version 7.4 + +<# +.SYNOPSIS +Returns the display label for a check id: the bare id for a shipped check, the +id suffixed with ' [custom]' for one registered by the machine-local overlay. + +.DESCRIPTION +One label format for every surface that names a check (report findings, the +summary table, the pre-run banner's coverage list), so a custom check reads the +same everywhere. + +Neutrally named: cross-OS formatting, no Windows-specific logic. +#> + +function Format-CheckLabel { + [CmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory = $true)] [string] $Id, + [AllowEmptyCollection()] + [string[]] $CustomIds = @() + ) + return ($CustomIds -contains $Id) ? "$Id [custom]" : $Id +} diff --git a/plugins/machine-health/skills/audit/scripts/windows/lib/Get-NonElevatedCoverage.ps1 b/plugins/machine-health/skills/audit/scripts/windows/lib/Get-NonElevatedCoverage.ps1 new file mode 100644 index 00000000000..a7d7d0427f8 --- /dev/null +++ b/plugins/machine-health/skills/audit/scripts/windows/lib/Get-NonElevatedCoverage.ps1 @@ -0,0 +1,32 @@ +#Requires -Version 7.4 + +<# +.SYNOPSIS +Returns the ordered labels of the catalog checks that run without elevation, +for the pre-run banner's "Covered non-elevated" list. + +.DESCRIPTION +Derived from the catalog (enabled entries with needs_admin: false), so the +banner cannot drift from the checks that actually run. Admin-gated fields of +those checks are listed separately by Get-ElevationMatrix. + +Neutrally named: cross-OS filter over catalog entries. +#> + +. (Join-Path $PSScriptRoot 'Format-CheckLabel.ps1') + +function Get-NonElevatedCoverage { + [CmdletBinding()] + [OutputType([object[]])] + param( + [Parameter(Mandatory = $true)] + [AllowEmptyCollection()] + [object[]] $Catalog, + [AllowEmptyCollection()] + [string[]] $CustomIds = @() + ) + + return @($Catalog | + Where-Object { $_.enabled -and $_.needs_admin -eq $false } | + ForEach-Object { Format-CheckLabel -Id ([string]$_.id) -CustomIds $CustomIds }) +} diff --git a/plugins/machine-health/skills/audit/tests/windows/lib/Format-CheckLabel.Tests.ps1 b/plugins/machine-health/skills/audit/tests/windows/lib/Format-CheckLabel.Tests.ps1 new file mode 100644 index 00000000000..9d9fea8cf42 --- /dev/null +++ b/plugins/machine-health/skills/audit/tests/windows/lib/Format-CheckLabel.Tests.ps1 @@ -0,0 +1,36 @@ +#Requires -Version 7.4 +#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.7.0' } +<# +.SYNOPSIS +Tests for scripts/windows/lib/Format-CheckLabel.ps1. +#> + +BeforeDiscovery { + . "$PSScriptRoot\..\..\helpers\Initialize-CheckSuite.ps1" -LibScript 'ConvertFrom-Jsonc.ps1' + $catalogText = Get-Content -LiteralPath (Join-Path $script:SkillRoot 'catalog\checks.jsonc') -Raw + $script:ShippedIds = @((ConvertFrom-Jsonc -InputText $catalogText).checks | + ForEach-Object { @{ Id = [string]$_.id } }) +} + +BeforeAll { + . "$PSScriptRoot\..\..\helpers\Initialize-CheckSuite.ps1" -LibScript 'Format-CheckLabel.ps1' +} + +Describe 'Format-CheckLabel' -Tag 'lib' { + It 'discovers the shipped catalog ids' -ForEach @(@{ Count = $script:ShippedIds.Count }) { + $Count | Should -BeGreaterThan 0 + } + + It 'labels shipped check with its bare id' -ForEach $script:ShippedIds { + Format-CheckLabel -Id $Id | Should -BeExactly $Id + } + + It 'labels shipped check with its bare id when other ids are custom' -ForEach $script:ShippedIds { + Format-CheckLabel -Id $Id -CustomIds @('my-custom-check') | Should -BeExactly $Id + } + + It 'marks a custom check id' { + Format-CheckLabel -Id 'my-custom-check' -CustomIds @('my-custom-check') | + Should -BeExactly 'my-custom-check [custom]' + } +} diff --git a/plugins/machine-health/skills/audit/tests/windows/lib/Get-NonElevatedCoverage.Tests.ps1 b/plugins/machine-health/skills/audit/tests/windows/lib/Get-NonElevatedCoverage.Tests.ps1 new file mode 100644 index 00000000000..35511c03dbc --- /dev/null +++ b/plugins/machine-health/skills/audit/tests/windows/lib/Get-NonElevatedCoverage.Tests.ps1 @@ -0,0 +1,34 @@ +#Requires -Version 7.4 +#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.7.0' } +<# +.SYNOPSIS +Tests for scripts/windows/lib/Get-NonElevatedCoverage.ps1. +#> + +BeforeAll { + . "$PSScriptRoot\..\..\helpers\Initialize-CheckSuite.ps1" -LibScript 'Get-NonElevatedCoverage.ps1' + + $script:Catalog = @( + [pscustomobject]@{ id = 'zeta-check'; enabled = $true; needs_admin = $false } + [pscustomobject]@{ id = 'admin-check'; enabled = $true; needs_admin = $true } + [pscustomobject]@{ id = 'disabled-check'; enabled = $false; needs_admin = $false } + [pscustomobject]@{ id = 'my-custom-check'; enabled = $true; needs_admin = $false } + [pscustomobject]@{ id = 'alpha-check'; enabled = $true; needs_admin = $false } + ) +} + +Describe 'Get-NonElevatedCoverage' -Tag 'lib' { + It 'lists enabled non-admin entries in catalog order' { + $labels = @(Get-NonElevatedCoverage -Catalog $script:Catalog) + ($labels -join '|') | Should -BeExactly 'zeta-check|my-custom-check|alpha-check' + } + + It 'marks custom entries the way the report does' { + $labels = @(Get-NonElevatedCoverage -Catalog $script:Catalog -CustomIds @('my-custom-check')) + ($labels -join '|') | Should -BeExactly 'zeta-check|my-custom-check [custom]|alpha-check' + } + + It 'returns an empty list for an empty catalog' { + @(Get-NonElevatedCoverage -Catalog @()).Count | Should -Be 0 + } +} diff --git a/plugins/machine-health/skills/audit/tests/windows/lib/Write-ElevationBanner.Tests.ps1 b/plugins/machine-health/skills/audit/tests/windows/lib/Write-ElevationBanner.Tests.ps1 index da27fbc96ff..96e0b331b7c 100644 --- a/plugins/machine-health/skills/audit/tests/windows/lib/Write-ElevationBanner.Tests.ps1 +++ b/plugins/machine-health/skills/audit/tests/windows/lib/Write-ElevationBanner.Tests.ps1 @@ -12,14 +12,20 @@ BeforeAll { # [Console]::Error.WriteLine bypasses the error stream, so `2>&1` captures nothing; # swap in a StringWriter to capture real stderr text. function Invoke-BannerCapture { - param([Parameter(Mandatory)] [bool] $Elevated, [switch] $Quiet, [string] $StateBase) + param( + [Parameter(Mandatory)] [bool] $Elevated, + [switch] $Quiet, + [string] $StateBase, + [string[]] $Coverage = @() + ) $writer = [System.IO.StringWriter]::new() $saved = [Console]::Error try { [Console]::SetError($writer) Write-ElevationBanner -Elevated $Elevated -HostName 'HOST' ` -UserName 'DOMAIN\user' -OutputBase 'C:\out' ` - -StateBase $StateBase -SkillRoot 'C:\skill' -Matrix @(Get-ElevationMatrix) -Quiet:$Quiet + -StateBase $StateBase -SkillRoot 'C:\skill' -Matrix @(Get-ElevationMatrix) ` + -NonElevatedCoverage $Coverage -Quiet:$Quiet } finally { [Console]::SetError($saved) } @@ -42,6 +48,19 @@ Describe 'Write-ElevationBanner' -Tag 'lib' { $err | Should -Match ([regex]::Escape("-StateBase 'C:\state'")) } + It 'lists each derived coverage label under Covered non-elevated' { + $err = Invoke-BannerCapture -Elevated $false -Coverage @('disk-space', 'my-check [custom]') + $err | Should -Match ([regex]::Escape(' Covered non-elevated:')) + $err | Should -Match ([regex]::Escape(' - disk-space')) + $err | Should -Match ([regex]::Escape(' - my-check [custom]')) + } + + It 'omits the coverage section when the derived list is empty' { + $err = Invoke-BannerCapture -Elevated $false + $err | Should -Not -Match 'Covered non-elevated' + $err | Should -Match 'Admin-only' + } + It 'emits nothing when elevated' { $err = Invoke-BannerCapture -Elevated $true $err | Should -BeNullOrEmpty From f12a8f8120ffd9059a6c02540c6408ea9b510c6e Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:43:54 -0400 Subject: [PATCH 2/2] refactor(machine-health): tighten the coverage helpers after code-design review Get-NonElevatedCoverage filters only on needs_admin, since the caller passes the run set; Format-CheckLabel requires -CustomIds so a call that omits it fails instead of mislabeling custom checks. Its tests use literal cases. The elevation-matrix doc, the banner docstring and the Test-Drivers comment cite the helpers by name instead of restating them. Co-Authored-By: Claude Opus 5.5 --- .../reference/windows/elevation-matrix.md | 2 +- .../scripts/windows/checks/Test-Drivers.ps1 | 2 +- .../scripts/windows/lib/Format-CheckLabel.ps1 | 3 +- .../windows/lib/Get-NonElevatedCoverage.ps1 | 9 ++--- .../windows/lib/Write-ElevationBanner.ps1 | 2 +- .../windows/lib/Format-CheckLabel.Tests.ps1 | 35 +++++++++++-------- .../lib/Get-NonElevatedCoverage.Tests.ps1 | 11 +++--- 7 files changed, 35 insertions(+), 29 deletions(-) diff --git a/plugins/machine-health/skills/audit/reference/windows/elevation-matrix.md b/plugins/machine-health/skills/audit/reference/windows/elevation-matrix.md index b437423013a..57604cc7bc0 100644 --- a/plugins/machine-health/skills/audit/reference/windows/elevation-matrix.md +++ b/plugins/machine-health/skills/audit/reference/windows/elevation-matrix.md @@ -11,7 +11,7 @@ Which Windows capabilities the skill can and cannot exercise when the process is ## Capabilities that require elevation -`scripts/windows/lib/Get-ElevationMatrix.ps1` is the one list: each row names the feature, the check that consumes it, the fields it populates when elevated, and the reason elevation is required. The pre-run banner and the report's elevation-coverage block render from it. The banner's "Covered non-elevated" list is derived from the catalog: enabled entries with `needs_admin: false`. +See `Get-ElevationMatrix` (`scripts/windows/lib/Get-ElevationMatrix.ps1`) for the admin-gated capabilities, and `Get-NonElevatedCoverage` (`scripts/windows/lib/Get-NonElevatedCoverage.ps1`) for the banner's "Covered non-elevated" list. ## How to run elevated diff --git a/plugins/machine-health/skills/audit/scripts/windows/checks/Test-Drivers.ps1 b/plugins/machine-health/skills/audit/scripts/windows/checks/Test-Drivers.ps1 index caebfff6c9a..5281b5aed94 100644 --- a/plugins/machine-health/skills/audit/scripts/windows/checks/Test-Drivers.ps1 +++ b/plugins/machine-health/skills/audit/scripts/windows/checks/Test-Drivers.ps1 @@ -145,7 +145,7 @@ function Invoke-DriversCheck { } # Admin-gated (pnputil problem devices, PSWindowsUpdate driver catalog): non-elevated - # runs emit these fields as null with needs_admin: true. See elevation-matrix.md. + # runs emit these fields as null with needs_admin: true. See lib/Get-ElevationMatrix.ps1. $elevated = Test-IsElevated $adminFields = [System.Collections.Generic.List[string]]::new() diff --git a/plugins/machine-health/skills/audit/scripts/windows/lib/Format-CheckLabel.ps1 b/plugins/machine-health/skills/audit/scripts/windows/lib/Format-CheckLabel.ps1 index f2f98188141..828dd404397 100644 --- a/plugins/machine-health/skills/audit/scripts/windows/lib/Format-CheckLabel.ps1 +++ b/plugins/machine-health/skills/audit/scripts/windows/lib/Format-CheckLabel.ps1 @@ -18,8 +18,9 @@ function Format-CheckLabel { [OutputType([string])] param( [Parameter(Mandatory = $true)] [string] $Id, + [Parameter(Mandatory = $true)] [AllowEmptyCollection()] - [string[]] $CustomIds = @() + [string[]] $CustomIds ) return ($CustomIds -contains $Id) ? "$Id [custom]" : $Id } diff --git a/plugins/machine-health/skills/audit/scripts/windows/lib/Get-NonElevatedCoverage.ps1 b/plugins/machine-health/skills/audit/scripts/windows/lib/Get-NonElevatedCoverage.ps1 index a7d7d0427f8..a46979db839 100644 --- a/plugins/machine-health/skills/audit/scripts/windows/lib/Get-NonElevatedCoverage.ps1 +++ b/plugins/machine-health/skills/audit/scripts/windows/lib/Get-NonElevatedCoverage.ps1 @@ -6,9 +6,10 @@ Returns the ordered labels of the catalog checks that run without elevation, for the pre-run banner's "Covered non-elevated" list. .DESCRIPTION -Derived from the catalog (enabled entries with needs_admin: false), so the -banner cannot drift from the checks that actually run. Admin-gated fields of -those checks are listed separately by Get-ElevationMatrix. +The caller passes the run set (Windows, enabled, not deprecated entries), taken +before cadence selection; this keeps the entries with needs_admin: false, so the +banner cannot drift from the catalog. Admin-gated fields of those checks are +listed separately by Get-ElevationMatrix. Neutrally named: cross-OS filter over catalog entries. #> @@ -27,6 +28,6 @@ function Get-NonElevatedCoverage { ) return @($Catalog | - Where-Object { $_.enabled -and $_.needs_admin -eq $false } | + Where-Object { $_.needs_admin -eq $false } | ForEach-Object { Format-CheckLabel -Id ([string]$_.id) -CustomIds $CustomIds }) } diff --git a/plugins/machine-health/skills/audit/scripts/windows/lib/Write-ElevationBanner.ps1 b/plugins/machine-health/skills/audit/scripts/windows/lib/Write-ElevationBanner.ps1 index a00c8b29fde..babe53cf579 100644 --- a/plugins/machine-health/skills/audit/scripts/windows/lib/Write-ElevationBanner.ps1 +++ b/plugins/machine-health/skills/audit/scripts/windows/lib/Write-ElevationBanner.ps1 @@ -26,7 +26,7 @@ Banner structure: Running as without admin. Covered non-elevated: - - + - Admin-only (will emit UNKNOWN with needs_admin: true): - [] diff --git a/plugins/machine-health/skills/audit/tests/windows/lib/Format-CheckLabel.Tests.ps1 b/plugins/machine-health/skills/audit/tests/windows/lib/Format-CheckLabel.Tests.ps1 index 9d9fea8cf42..c37fe19c568 100644 --- a/plugins/machine-health/skills/audit/tests/windows/lib/Format-CheckLabel.Tests.ps1 +++ b/plugins/machine-health/skills/audit/tests/windows/lib/Format-CheckLabel.Tests.ps1 @@ -5,32 +5,37 @@ Tests for scripts/windows/lib/Format-CheckLabel.ps1. #> -BeforeDiscovery { - . "$PSScriptRoot\..\..\helpers\Initialize-CheckSuite.ps1" -LibScript 'ConvertFrom-Jsonc.ps1' - $catalogText = Get-Content -LiteralPath (Join-Path $script:SkillRoot 'catalog\checks.jsonc') -Raw - $script:ShippedIds = @((ConvertFrom-Jsonc -InputText $catalogText).checks | - ForEach-Object { @{ Id = [string]$_.id } }) -} - BeforeAll { . "$PSScriptRoot\..\..\helpers\Initialize-CheckSuite.ps1" -LibScript 'Format-CheckLabel.ps1' } Describe 'Format-CheckLabel' -Tag 'lib' { - It 'discovers the shipped catalog ids' -ForEach @(@{ Count = $script:ShippedIds.Count }) { - $Count | Should -BeGreaterThan 0 - } - - It 'labels shipped check with its bare id' -ForEach $script:ShippedIds { - Format-CheckLabel -Id $Id | Should -BeExactly $Id + It 'labels a shipped check with its bare id' { + Format-CheckLabel -Id 'disk-space' -CustomIds @() | Should -BeExactly 'disk-space' } - It 'labels shipped check with its bare id when other ids are custom' -ForEach $script:ShippedIds { - Format-CheckLabel -Id $Id -CustomIds @('my-custom-check') | Should -BeExactly $Id + It 'labels a shipped check with its bare id when another id is custom' { + Format-CheckLabel -Id 'disk-space' -CustomIds @('my-custom-check') | Should -BeExactly 'disk-space' } It 'marks a custom check id' { Format-CheckLabel -Id 'my-custom-check' -CustomIds @('my-custom-check') | Should -BeExactly 'my-custom-check [custom]' } + + It 'fails when -CustomIds is omitted' { + # A runspace with no host UI cannot prompt, so a missing mandatory + # parameter fails instead of waiting for input. + $ps = [powershell]::Create() + try { + [void]$ps.AddScript(". '$(Join-Path $script:LibRoot 'Format-CheckLabel.ps1')'; Format-CheckLabel -Id 'disk-space'") + $output = $ps.Invoke() + $output | Should -BeNullOrEmpty + $ps.Streams.Error.Count | Should -Be 1 + $ps.Streams.Error[0].FullyQualifiedErrorId | Should -BeLike 'MissingMandatoryParameter*' + $ps.Streams.Error[0].Exception.Message | Should -BeLike '*CustomIds*' + } finally { + $ps.Dispose() + } + } } diff --git a/plugins/machine-health/skills/audit/tests/windows/lib/Get-NonElevatedCoverage.Tests.ps1 b/plugins/machine-health/skills/audit/tests/windows/lib/Get-NonElevatedCoverage.Tests.ps1 index 35511c03dbc..054961c390f 100644 --- a/plugins/machine-health/skills/audit/tests/windows/lib/Get-NonElevatedCoverage.Tests.ps1 +++ b/plugins/machine-health/skills/audit/tests/windows/lib/Get-NonElevatedCoverage.Tests.ps1 @@ -9,16 +9,15 @@ BeforeAll { . "$PSScriptRoot\..\..\helpers\Initialize-CheckSuite.ps1" -LibScript 'Get-NonElevatedCoverage.ps1' $script:Catalog = @( - [pscustomobject]@{ id = 'zeta-check'; enabled = $true; needs_admin = $false } - [pscustomobject]@{ id = 'admin-check'; enabled = $true; needs_admin = $true } - [pscustomobject]@{ id = 'disabled-check'; enabled = $false; needs_admin = $false } - [pscustomobject]@{ id = 'my-custom-check'; enabled = $true; needs_admin = $false } - [pscustomobject]@{ id = 'alpha-check'; enabled = $true; needs_admin = $false } + [pscustomobject]@{ id = 'zeta-check'; needs_admin = $false } + [pscustomobject]@{ id = 'admin-check'; needs_admin = $true } + [pscustomobject]@{ id = 'my-custom-check'; needs_admin = $false } + [pscustomobject]@{ id = 'alpha-check'; needs_admin = $false } ) } Describe 'Get-NonElevatedCoverage' -Tag 'lib' { - It 'lists enabled non-admin entries in catalog order' { + It 'lists the non-admin entries of the run set in catalog order' { $labels = @(Get-NonElevatedCoverage -Catalog $script:Catalog) ($labels -join '|') | Should -BeExactly 'zeta-check|my-custom-check|alpha-check' }