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
11 changes: 1 addition & 10 deletions modules/events/diagnostic.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,7 @@ function Group-EventLogEvents {
$levelDisplayName = [string]$level
}

$representativeMessage = [string]$representativeEvent.Message
if ([string]::IsNullOrWhiteSpace($representativeMessage)) {
$representativeMessage = 'No message'
}
else {
$representativeMessage = (($representativeMessage -replace '\s+', ' ').Trim())
if ($representativeMessage.Length -gt 240) {
$representativeMessage = $representativeMessage.Substring(0, 237) + '...'
}
}
$representativeMessage = ConvertTo-OneLineMessage -Message ([string]$representativeEvent.Message)

$logNames = @(
$orderedEvents |
Expand Down
2 changes: 1 addition & 1 deletion modules/performance/diagnostic.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ function Invoke-PerformanceSnapshot {
else {
Write-Host 'Method : average of 3 short samples'
Write-Host ('Total CPU load : {0:N1}%' -f $cpu.Percent)
Write-Host ('Samples : {0}' -f (($cpu.Samples | ForEach-Object { '{0:N1}%' -f $_ }) -join ', '))
Write-Host ('Samples : {0}' -f (($cpu.Samples | ForEach-Object { '{0:N1}%' -f $_ }) -join ', '))
}

Write-Section 'Pagefile Usage'
Expand Down
14 changes: 7 additions & 7 deletions modules/services/diagnostic.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -235,15 +235,15 @@ function Get-StartupRegistrySourceInventory {
if ($null -eq $ApprovalInventory) {
$ApprovalInventory = Get-StartupApprovalInventory -Path $Definition.ApprovalLocation
}
if (-not [string]::IsNullOrWhiteSpace([string]$approvalInventory.Error)) {
$errors.Add([pscustomobject]@{ Path = $approvalInventory.Path; Error = $approvalInventory.Error })
if (-not [string]::IsNullOrWhiteSpace([string]$ApprovalInventory.Error)) {
$errors.Add([pscustomobject]@{ Path = $ApprovalInventory.Path; Error = $ApprovalInventory.Error })
}

try {
if (Test-Path -LiteralPath $Definition.Location) {
$item = Get-ItemProperty -LiteralPath $Definition.Location -ErrorAction Stop
foreach ($property in @($item.PSObject.Properties | Where-Object { $_.Name -notin @('PSPath', 'PSParentPath', 'PSChildName', 'PSDrive', 'PSProvider') })) {
$state = Resolve-StartupEntryState -Name $property.Name -ApprovalInventory $approvalInventory
$state = Resolve-StartupEntryState -Name $property.Name -ApprovalInventory $ApprovalInventory
$entries.Add([pscustomobject]@{
State = $state.State
StateSource = $state.StateSource
Expand Down Expand Up @@ -273,14 +273,14 @@ function Get-StartupFolderSourceInventory {
if ($null -eq $ApprovalInventory) {
$ApprovalInventory = Get-StartupApprovalInventory -Path $Definition.ApprovalLocation
}
if (-not [string]::IsNullOrWhiteSpace([string]$approvalInventory.Error)) {
$errors.Add([pscustomobject]@{ Path = $approvalInventory.Path; Error = $approvalInventory.Error })
if (-not [string]::IsNullOrWhiteSpace([string]$ApprovalInventory.Error)) {
$errors.Add([pscustomobject]@{ Path = $ApprovalInventory.Path; Error = $ApprovalInventory.Error })
}

try {
if (Test-Path -LiteralPath $Definition.Location -PathType Container) {
foreach ($file in @(Get-ChildItem -LiteralPath $Definition.Location -Force -File -ErrorAction Stop)) {
$state = Resolve-StartupEntryState -Name $file.Name -ApprovalInventory $approvalInventory
$state = Resolve-StartupEntryState -Name $file.Name -ApprovalInventory $ApprovalInventory
$entries.Add([pscustomobject]@{
State = $state.State
StateSource = $state.StateSource
Expand Down Expand Up @@ -536,7 +536,7 @@ else {
Write-Host ('Total services : {0}' -f $services.Count)
Write-Host ('Running services : {0}' -f $runningServices.Count)
Write-Host ('Auto + Stopped (Indeterminate) : {0}' -f $automaticNotRunning.Count)
Write-Host ('Pending states (Suspicious) : {0}' -f $suspiciousServiceStates.Count)
Write-Host ('Pending states (Suspicious) : {0}' -f $suspiciousServiceStates.Count)
Write-Host ('Confirmed service problems : {0}' -f $confirmedServiceProblems.Count)
}

Expand Down
6 changes: 3 additions & 3 deletions scripts/process-runner.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,11 @@ function Test-WdtSnapshotMembership {
}
if ($identity.Status -ne $script:WdtCleanupStatus.Match) { return $identity }
if ($cursor.IsRoot) { return New-WdtStatusResult -Status $script:WdtCleanupStatus.Match }
if (-not $SnapshotById.ContainsKey([int]$cursor.ParentProcessId)) { return New-WdtStatusResult -Status $script:WdtCleanupStatus.SnapshotParentMissing -Message 'Expected parent is absent from the cleanup snapshot.' }
$cursor = $SnapshotById[[int]$cursor.ParentProcessId]
$parentId = [int]$cursor.ParentProcessId
if (-not $SnapshotById.ContainsKey($parentId) -or $null -eq $SnapshotById[$parentId]) { return New-WdtStatusResult -Status $script:WdtCleanupStatus.SnapshotParentMissing -Message 'Expected parent is absent from the cleanup snapshot.' }
$cursor = $SnapshotById[$parentId]
$isTarget = $false
}
return New-WdtStatusResult -Status $script:WdtCleanupStatus.SnapshotParentMissing -Message 'Expected parent is absent from the cleanup snapshot.'
}

function Get-WdtProcessCleanupSummary {
Expand Down
14 changes: 7 additions & 7 deletions scripts/report-common.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,15 @@ function Protect-WdtRegexMatches {
[scriptblock]$TokenValueSelector
)

$matches = $Regex.Matches($Text)
if ($matches.Count -eq 0) {
$regexMatches = $Regex.Matches($Text)
if ($regexMatches.Count -eq 0) {
return $Text
}

$builder = New-Object System.Text.StringBuilder
$nextIndex = 0

foreach ($match in $matches) {
foreach ($match in $regexMatches) {
$valueMatch = $match
if (-not [string]::IsNullOrWhiteSpace($CaptureGroupName)) {
$valueMatch = $match.Groups[$CaptureGroupName]
Expand Down Expand Up @@ -784,9 +784,9 @@ function Get-WdtFindingsSummary {

return [pscustomobject][ordered]@{
OverallStatus = $overallStatus
ErrorCount = $errors.Count
WarningCount = $warnings.Count
OkModuleCount = $okItems.Count
Items = @($errors + $warnings + $okItems)
ErrorCount = $errors.Count
WarningCount = $warnings.Count
OkModuleCount = $okItems.Count
Items = @($errors + $warnings + $okItems)
}
}
14 changes: 4 additions & 10 deletions scripts/tui.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -332,29 +332,23 @@ function Join-WdtTuiColumns {
function Test-WdtTuiUnicodeLogoSupport {
param(
[bool]$IsOutputRedirected,
[bool]$HasWtSession,
[string]$TermProgram,
[string]$OutputEncodingWebName
)

if ($IsOutputRedirected) { return $false }
$hasUtf8Output = $OutputEncodingWebName -in @('utf-8', 'utf8')
$hasWindowsTerminalSignal = $HasWtSession -or $TermProgram -eq 'Windows_Terminal'
return $hasUtf8Output -and ($hasWindowsTerminalSignal -or $hasUtf8Output)
return $OutputEncodingWebName -in @('utf-8', 'utf8')
}

function Get-WdtTuiLogoModeDecision {
param(
[bool]$IsOutputRedirected,
[bool]$HasWtSession,
[string]$TermProgram,
[string]$OutputEncodingWebName,
[string]$Override
)

if ($Override -ieq 'ascii') { return 'Ascii' }
if ($Override -ieq 'unicode') { return $(if ($IsOutputRedirected) { 'Ascii' } else { 'Unicode' }) }
if (Test-WdtTuiUnicodeLogoSupport -IsOutputRedirected $IsOutputRedirected -HasWtSession $HasWtSession -TermProgram $TermProgram -OutputEncodingWebName $OutputEncodingWebName) {
if (Test-WdtTuiUnicodeLogoSupport -IsOutputRedirected $IsOutputRedirected -OutputEncodingWebName $OutputEncodingWebName) {
return 'Unicode'
}
return 'Ascii'
Expand All @@ -368,7 +362,7 @@ function Get-WdtTuiLogoMode {
if ($override -ine 'unicode') {
$encodingWebName = [System.Console]::OutputEncoding.WebName
}
return Get-WdtTuiLogoModeDecision -IsOutputRedirected $isOutputRedirected -HasWtSession (-not [string]::IsNullOrWhiteSpace($env:WT_SESSION)) -TermProgram ([string]$env:TERM_PROGRAM) -OutputEncodingWebName $encodingWebName -Override $override
return Get-WdtTuiLogoModeDecision -IsOutputRedirected $isOutputRedirected -OutputEncodingWebName $encodingWebName -Override $override
}
catch { }
return 'Ascii'
Expand Down Expand Up @@ -1187,7 +1181,7 @@ function Invoke-WdtInteractiveSession {
$inputEvent = Wait-WdtTuiEvent -InitialWidth $size.Width -InitialHeight $size.Height
if ($inputEvent.Type -eq 'Resize') {
Reset-WdtTuiFrame
$layout = Show-WdtTuiScreen -State $state -Width $inputEvent.Size.Width -Height $inputEvent.Size.Height -ShowItemNumbers $false -ForceFull $true
$null = Show-WdtTuiScreen -State $state -Width $inputEvent.Size.Width -Height $inputEvent.Size.Height -ShowItemNumbers $false -ForceFull $true
continue
}
$keyInfo = $inputEvent.KeyInfo
Expand Down
15 changes: 7 additions & 8 deletions scripts/validation-policy.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,7 @@ function Test-WdtAllowedW32tmProcessShape {
param(
[Parameter(Mandatory = $true)][System.Management.Automation.Language.Ast]$Ast,
[string]$ScriptPath,
[string]$RepositoryRoot,
[string[]]$LocalFunctionNames
[string]$RepositoryRoot
)

if (-not (Test-WdtScriptPath $ScriptPath $RepositoryRoot 'modules\time\diagnostic.ps1')) { return $false }
Expand Down Expand Up @@ -468,7 +467,7 @@ function Get-WdtNewObjectTypeName {
}

function Test-WdtAllowedNewObjectCommand {
param($CommandAst, [string]$ScriptPath, [string]$RepositoryRoot, [string[]]$LocalFunctionNames)
param($CommandAst, [string]$ScriptPath, [string]$RepositoryRoot)

if ($CommandAst.Redirections.Count -ne 0) { return $false }
$elements = @($CommandAst.CommandElements)
Expand Down Expand Up @@ -528,7 +527,7 @@ function Test-WdtAllowedNewObjectCommand {
(Test-WdtScriptPath $ScriptPath $RepositoryRoot 'modules\time\diagnostic.ps1') -and
(Get-WdtEnclosingFunctionName $CommandAst) -ceq 'Invoke-W32tmQuery' -and
$CommandAst.CommandElements.Count -eq 2 -and
(Test-WdtAllowedW32tmProcessShape -Ast $CommandAst -ScriptPath $ScriptPath -RepositoryRoot $RepositoryRoot -LocalFunctionNames $LocalFunctionNames)) {
(Test-WdtAllowedW32tmProcessShape -Ast $CommandAst -ScriptPath $ScriptPath -RepositoryRoot $RepositoryRoot)) {
return $true
}

Expand Down Expand Up @@ -723,7 +722,7 @@ function Get-WdtCommandSafetyIssue {
}

if ($leaf -eq 'New-Object') {
if (Test-WdtAllowedNewObjectCommand $CommandAst $ScriptPath $RepositoryRoot $LocalFunctionNames) { return }
if (Test-WdtAllowedNewObjectCommand $CommandAst $ScriptPath $RepositoryRoot) { return }
return New-WdtSafetyIssue $CommandAst 'New-Object type is not in the reviewed safe-type allowlist.'
}

Expand Down Expand Up @@ -752,7 +751,7 @@ function Get-WdtCommandSafetyIssue {
}

function Get-WdtMemberSafetyIssue {
param($MemberAst, [string]$ScriptPath, [string]$RepositoryRoot, [string[]]$LocalFunctionNames)
param($MemberAst, [string]$ScriptPath, [string]$RepositoryRoot)
$member = [string]$MemberAst.Member.Value
$argumentCount = $MemberAst.Arguments.Count
$isStatic = $MemberAst.Expression -is [System.Management.Automation.Language.TypeExpressionAst]
Expand Down Expand Up @@ -889,7 +888,7 @@ function Get-WdtMemberSafetyIssue {
$member -in @('ConnectAsync', 'Wait', 'Dispose')) { return }
if ((Test-WdtScriptPath $ScriptPath $RepositoryRoot 'modules\time\diagnostic.ps1') -and
(Get-WdtEnclosingFunctionName $MemberAst) -ceq 'Invoke-W32tmQuery' -and
(Test-WdtAllowedW32tmProcessShape -Ast $MemberAst -ScriptPath $ScriptPath -RepositoryRoot $RepositoryRoot -LocalFunctionNames $LocalFunctionNames) -and
(Test-WdtAllowedW32tmProcessShape -Ast $MemberAst -ScriptPath $ScriptPath -RepositoryRoot $RepositoryRoot) -and
(($member -eq 'Start' -and $receiver -ceq 'process' -and $argumentCount -eq 0) -or
($member -eq 'Dispose' -and $receiver -in @('process', 'stdoutReader', 'stderrReader') -and $argumentCount -eq 0))) {
return
Expand Down Expand Up @@ -986,7 +985,7 @@ function Get-WdtSafetyIssues {
}
$localFunctionNames = @($localFunctionNames | Sort-Object -Unique)
foreach ($command in @($Ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.CommandAst] }, $true))) { Get-WdtCommandSafetyIssue $command $ScriptPath $RepositoryRoot $localFunctionNames $RegistrySnapshot $ModuleDefinition }
foreach ($member in @($Ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.InvokeMemberExpressionAst] }, $true))) { Get-WdtMemberSafetyIssue $member $ScriptPath $RepositoryRoot $localFunctionNames }
foreach ($member in @($Ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.InvokeMemberExpressionAst] }, $true))) { Get-WdtMemberSafetyIssue $member $ScriptPath $RepositoryRoot }
foreach ($member in @($Ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.MemberExpressionAst] -and $n -isnot [System.Management.Automation.Language.InvokeMemberExpressionAst] }, $true))) { Get-WdtConsolePropertySafetyIssue $member $ScriptPath $RepositoryRoot }
foreach ($variable in @($Ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.VariableExpressionAst] -and $n.VariablePath.UserPath -like 'function:*' }, $true))) {
New-WdtSafetyIssue $variable 'Dynamic function provider access is not allowed in production scripts.'
Expand Down
14 changes: 13 additions & 1 deletion tests/diagnostic-classification.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ $now = Get-Date
# Event Log fixtures: severity alone is context; exact rules, grouping, cutoff,
# provider identity, and partial source availability are deterministic.
$eventsScript = Join-Path $repositoryRoot 'modules\events\diagnostic.ps1'
Import-TestFunctions $eventsScript @('Get-EventSignalRule','Group-EventLogEvents','Read-EventLog')
Import-TestFunctions $eventsScript @('ConvertTo-OneLineMessage','Get-EventSignalRule','Group-EventLogEvents','Read-EventLog')
Assert-True ($null -eq (Get-EventSignalRule 'Application' 'Fixture-Provider' 1000 2)) 'A generic Error event must remain context.'
Assert-True ($null -eq (Get-EventSignalRule 'System' 'Microsoft-Windows-DistributedCOM' 10016 2)) 'Expected DCOM 10016 noise must remain context.'
Assert-True ($null -eq (Get-EventSignalRule 'System' 'Fixture-Kernel-Power' 41 1)) 'Event ID 41 from another provider must not match.'
Expand All @@ -44,6 +44,18 @@ Assert-Equal 3 $repeatedEventGroup.Count 'Repeated events must be grouped.'
Assert-Equal 'representative' $repeatedEventGroup.RepresentativeMessage 'The latest event must provide the representative message.'
Assert-True (-not $repeatedEventGroup.IsSignal) 'A grouped generic Error event must not become a finding.'

$messageFixtures = @(
[pscustomobject]@{ ProviderName='Fixture-Whitespace'; Id=7100; Level=2; LevelDisplayName='Error'; LogName='Application'; TimeCreated=$now.AddHours(-1); Message=" `r`n`t "; RecordId=6 },
[pscustomobject]@{ ProviderName='Fixture-LongMessage'; Id=7101; Level=2; LevelDisplayName='Error'; LogName='Application'; TimeCreated=$now.AddHours(-1); Message=("line1`r`nline2 " + ('x' * 300)); RecordId=7 }
)
$messageGroups = @(Group-EventLogEvents $messageFixtures $eventCutoff)
$whitespaceGroup = @($messageGroups | Where-Object { $_.ProviderName -eq 'Fixture-Whitespace' })[0]
Assert-Equal 'No message' $whitespaceGroup.RepresentativeMessage 'A whitespace-only message must normalize to No message.'
$longGroup = @($messageGroups | Where-Object { $_.ProviderName -eq 'Fixture-LongMessage' })[0]
Assert-Equal 240 $longGroup.RepresentativeMessage.Length 'A long message must be truncated to 240 characters.'
Assert-True ($longGroup.RepresentativeMessage.EndsWith('...')) 'A truncated message must end with an ellipsis.'
Assert-True ($longGroup.RepresentativeMessage.StartsWith('line1 line2 ')) 'Message whitespace runs must collapse to single spaces.'

function script:Get-WinEvent {
[CmdletBinding()]
param([hashtable]$FilterHashtable, [int]$MaxEvents)
Expand Down
4 changes: 4 additions & 0 deletions tests/process-runner.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ $childEntry = [pscustomobject]@{ ProcessId=200; ParentProcessId=100; Depth=1; Is
$watch = [Diagnostics.Stopwatch]::StartNew()
$membership = & { function Get-CimInstance { param($ClassName,$Filter,$OperationTimeoutSec) @() }; Test-WdtSnapshotMembership $childEntry @{100=$rootEntry;200=$childEntry} $watch 500 }
Assert-Equal 'TargetNotFound' $membership.Status 'An exited descendant must be safe.'
$nullParentWatch = [Diagnostics.Stopwatch]::StartNew()
$nullParentMembership = & { function Get-CimInstance { param($ClassName,$Filter,$OperationTimeoutSec) [pscustomobject]@{ ProcessId=200; ParentProcessId=100; CreationDate=[datetime]'2024-01-01T00:00:01' } }; Test-WdtSnapshotMembership $childEntry @{100=$null;200=$childEntry} $nullParentWatch 500 }
Assert-True ($null -ne $nullParentMembership) 'A null parent snapshot value must still produce a structured result.'
Assert-Equal 'SnapshotParentMissing' $nullParentMembership.Status 'A null parent snapshot value must be structured as SnapshotParentMissing.'
$failedSummary = Get-WdtProcessCleanupSummary @([pscustomobject]@{Status='TerminationFailed'}) @('fixture') $false
Assert-Equal $false $failedSummary.Success 'Failed cleanup must be explicit.'

Expand Down
7 changes: 7 additions & 0 deletions tests/tui.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,11 @@ try { ConvertTo-WdtReportParameters -State $state -RegistrySnapshot $otherSnapsh
catch { $snapshotMismatchBlocked = $true }
Assert-True $snapshotMismatchBlocked 'Report conversion must reject a different registry snapshot.'

Assert-Equal 'Ascii' (Get-WdtTuiLogoModeDecision -IsOutputRedirected $false -OutputEncodingWebName 'utf-8' -Override 'ascii') 'The ascii override must force the ASCII logo.'
Assert-Equal 'Unicode' (Get-WdtTuiLogoModeDecision -IsOutputRedirected $false -OutputEncodingWebName 'ibm866' -Override 'unicode') 'The unicode override must apply in an interactive host.'
Assert-Equal 'Ascii' (Get-WdtTuiLogoModeDecision -IsOutputRedirected $true -OutputEncodingWebName 'utf-8' -Override 'unicode') 'The unicode override must stay ASCII for redirected output.'
Assert-Equal 'Unicode' (Get-WdtTuiLogoModeDecision -IsOutputRedirected $false -OutputEncodingWebName 'utf-8' -Override '') 'Interactive UTF-8 output must select the Unicode logo.'
Assert-Equal 'Ascii' (Get-WdtTuiLogoModeDecision -IsOutputRedirected $true -OutputEncodingWebName 'utf-8' -Override '') 'Redirected output must select the ASCII logo.'
Assert-Equal 'Ascii' (Get-WdtTuiLogoModeDecision -IsOutputRedirected $false -OutputEncodingWebName 'ibm866' -Override '') 'An OEM encoding must select the ASCII logo.'

Write-Host 'TUI snapshot, dynamic viewport, layout, and parameter conversion tests passed.'