Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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. |
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

Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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('')
Expand Down Expand Up @@ -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 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#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,
[Parameter(Mandatory = $true)]
[AllowEmptyCollection()]
[string[]] $CustomIds
)
return ($CustomIds -contains $Id) ? "$Id [custom]" : $Id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#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
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.
#>

. (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 { $_.needs_admin -eq $false } |
ForEach-Object { Format-CheckLabel -Id ([string]$_.id) -CustomIds $CustomIds })
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Banner structure:
Running as <user> without admin.

Covered non-elevated:
- <list of non-admin checks by category>
- <labels from Get-NonElevatedCoverage>

Admin-only (will emit UNKNOWN with needs_admin: true):
- <Feature> [<CheckId>]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#Requires -Version 7.4
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.7.0' }
<#
.SYNOPSIS
Tests for scripts/windows/lib/Format-CheckLabel.ps1.
#>

BeforeAll {
. "$PSScriptRoot\..\..\helpers\Initialize-CheckSuite.ps1" -LibScript 'Format-CheckLabel.ps1'
}

Describe 'Format-CheckLabel' -Tag 'lib' {
It 'labels a shipped check with its bare id' {
Format-CheckLabel -Id 'disk-space' -CustomIds @() | Should -BeExactly 'disk-space'
}

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()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#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'; 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 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'
}

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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
Expand Down
Loading