# Parse-ModernStandbyReport.ps1 # CLM-safe: built-in cmdlets + powercfg.exe only # Goal: find likely Modern Standby trigger/reason lines in SleepStudy/System Power Report $ErrorActionPreference = 'SilentlyContinue' $LogRoot = 'C:\temp\logs' if (-not (Test-Path $LogRoot)) { New-Item -Path $LogRoot -ItemType Directory -Force | Out-Null } $TimeStamp = Get-Date -Format 'yyyyMMdd-HHmmss' $LogPath = Join-Path $LogRoot "Parse-ModernStandbyReport-$($env:COMPUTERNAME)-$TimeStamp.log" $baseName = "sleepstudy-$($env:COMPUTERNAME)-$TimeStamp" $xmlPath = Join-Path $LogRoot ($baseName + '.xml') $htmlPath = Join-Path $LogRoot ($baseName + '.html') Start-Transcript -Path $LogPath -Force | Out-Null function Show-Section { param([string]$Title) Write-Output "" Write-Output ('=' * 96) Write-Output $Title Write-Output ('=' * 96) } function Get-NewestFile { param([string[]]$Paths) $all = foreach ($p in $Paths) { Get-ChildItem -Path $p -File -ErrorAction SilentlyContinue } $all | Sort-Object LastWriteTime -Descending | Select-Object -First 1 } function Get-ReportCandidates { $candidates = @() $paths = @( $LogRoot, $env:TEMP, (Get-Location).Path, "$env:WINDIR\System32" ) | Where-Object { $_ -and (Test-Path $_) } foreach ($path in $paths) { $candidates += Get-ChildItem -Path $path -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'sleepstudy.*\.html$' -or $_.Name -match 'sleepstudy.*\.xml$' -or $_.Name -match 'systempowerreport.*\.html$' -or $_.Name -match 'systempowerreport.*\.xml$' } } $candidates | Sort-Object LastWriteTime -Descending -Unique } function Save-FreshSleepStudyXml { param([string]$XmlPath) Show-Section 'Generating fresh SleepStudy XML' try { & powercfg.exe /sleepstudy /duration 3 /xml /output $XmlPath 2>&1 | ForEach-Object { $_ } if (Test-Path $XmlPath) { Write-Output "Saved: $XmlPath" return $true } else { Write-Output 'SleepStudy XML was not created.' return $false } } catch { Write-Output "FAILED: $($_.Exception.Message)" return $false } } function Save-TransformedHtml { param( [string]$XmlPath, [string]$HtmlPath ) Show-Section 'Transforming SleepStudy XML to HTML' try { & powercfg.exe /sleepstudy /transformxml $XmlPath /output $HtmlPath 2>&1 | ForEach-Object { $_ } if (Test-Path $HtmlPath) { Write-Output "Saved: $HtmlPath" return $true } else { Write-Output 'Transformed HTML was not created.' return $false } } catch { Write-Output "FAILED: $($_.Exception.Message)" return $false } } function Read-WholeFile { param([string]$Path) try { return Get-Content -Path $Path -Raw -Encoding UTF8 } catch { try { return Get-Content -Path $Path -Raw } catch { return $null } } } function Strip-Html { param([string]$Text) if (-not $Text) { return $null } $t = $Text $t = $t -replace '(?is)', ' ' $t = $t -replace '(?is)', ' ' $t = $t -replace '(?i)', "`n" $t = $t -replace '(?i)

||||', "`n" $t = $t -replace '(?is)<[^>]+>', ' ' $t = $t -replace ' ', ' ' $t = $t -replace '&', '&' $t = $t -replace '<', '<' $t = $t -replace '>', '>' $t = $t -replace '"', '"' $t = $t -replace ''', "'" $t = $t -replace '[ \t]+', ' ' $t = $t -replace ' *\r?\n *', "`n" $t = $t -replace "(`n){3,}", "`n`n" return $t.Trim() } function Get-ContextMatches { param( [string]$Text, [string[]]$Patterns, [int]$ContextLines = 2 ) if (-not $Text) { return @() } $lines = $Text -split "`r?`n" $hits = @() for ($i = 0; $i -lt $lines.Count; $i++) { foreach ($p in $Patterns) { if ($lines[$i] -match $p) { $start = [Math]::Max(0, $i - $ContextLines) $end = [Math]::Min($lines.Count - 1, $i + $ContextLines) $block = for ($j = $start; $j -le $end; $j++) { if ($j -eq $i) { ">> " + $lines[$j].Trim() } else { " " + $lines[$j].Trim() } } $hits += [PSCustomObject]@{ LineNumber = $i + 1 Pattern = $p Context = ($block -join "`n") } break } } } $hits } function Get-ReasonSummary { param([string]$Text) if (-not $Text) { return @() } $reasonPatterns = @( 'Idle Timeout', 'Session Unlock', 'Session Lock', 'Input Keyboard', 'Input Mouse', 'InputHid', 'Lid', 'Human Presence', 'Lock on Leave', 'Display Burst', 'Austerity Battery Drain Budget Exceeded', 'Transition To Sleep', 'Enter Reason', 'Exit Reason', 'Wake Source', 'Wake Reason', 'Modern Standby', 'Screen Off', 'Sleep' ) $summary = foreach ($p in $reasonPatterns) { $count = ([regex]::Matches($Text, [regex]::Escape($p), [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)).Count if ($count -gt 0) { [PSCustomObject]@{ Term = $p Count = $count } } } $summary | Sort-Object -Property @{Expression='Count';Descending=$true}, @{Expression='Term';Descending=$false} } function Show-RecentKernelPower { Show-Section 'Recent Kernel-Power 506/507 cross-check' try { Get-WinEvent -FilterHashtable @{ LogName = 'System' Id = 506,507 StartTime = (Get-Date).AddDays(-3) } -MaxEvents 30 | Select-Object TimeCreated, Id, ProviderName, Message | Format-Table -Wrap -AutoSize | Out-String -Width 240 | Write-Output } catch { Write-Output 'Could not read System log.' } } Write-Output "PowerShell LanguageMode : $($ExecutionContext.SessionState.LanguageMode)" Write-Output "Computer Name : $env:COMPUTERNAME" Write-Output "User : $env:USERNAME" Write-Output "Time : $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" Write-Output "Log folder : $LogRoot" $generatedXml = Save-FreshSleepStudyXml -XmlPath $xmlPath if ($generatedXml) { [void](Save-TransformedHtml -XmlPath $xmlPath -HtmlPath $htmlPath) } $candidates = Get-ReportCandidates Show-Section 'Report candidates found' if ($candidates) { $candidates | Select-Object LastWriteTime, FullName | Format-Table -AutoSize | Out-String -Width 240 | Write-Output } else { Write-Output '' } $latestXml = $candidates | Where-Object { $_.Extension -eq '.xml' } | Sort-Object LastWriteTime -Descending | Select-Object -First 1 $latestHtml = $candidates | Where-Object { $_.Extension -eq '.html' } | Sort-Object LastWriteTime -Descending | Select-Object -First 1 Show-Section 'Selected files' Write-Output ("Latest XML : " + $(if ($latestXml) { $latestXml.FullName } else { '' })) Write-Output ("Latest HTML : " + $(if ($latestHtml) { $latestHtml.FullName } else { '' })) $xmlText = $null $htmlText = $null $plainHtml = $null if ($latestXml) { $xmlText = Read-WholeFile -Path $latestXml.FullName } if ($latestHtml) { $htmlText = Read-WholeFile -Path $latestHtml.FullName $plainHtml = Strip-Html -Text $htmlText } Show-Section 'Reason summary from XML' $xmlSummary = Get-ReasonSummary -Text $xmlText if ($xmlSummary) { $xmlSummary | Format-Table -AutoSize | Out-String -Width 240 | Write-Output } else { Write-Output '' } Show-Section 'Reason summary from HTML/plain text' $htmlSummary = Get-ReasonSummary -Text $plainHtml if ($htmlSummary) { $htmlSummary | Format-Table -AutoSize | Out-String -Width 240 | Write-Output } else { Write-Output '' } $patterns = @( 'Idle Timeout', 'Enter Reason', 'Exit Reason', 'Wake Source', 'Wake Reason', 'Session Unlock', 'Session Lock', 'Input Keyboard', 'Input Mouse', 'InputHid', 'Lid', 'Human Presence', 'Lock on Leave', 'Display Burst', 'Austerity Battery Drain Budget Exceeded', 'Transition To Sleep', 'Modern Standby', 'Screen Off', 'Sleep' ) Show-Section 'Likely trigger lines from XML' $xmlHits = Get-ContextMatches -Text $xmlText -Patterns $patterns -ContextLines 2 if ($xmlHits) { $xmlHits | Select-Object -First 80 | ForEach-Object { "[Line $($_.LineNumber)]" $_.Context "" } } else { Write-Output '' } Show-Section 'Likely trigger lines from HTML/plain text' $htmlHits = Get-ContextMatches -Text $plainHtml -Patterns $patterns -ContextLines 2 if ($htmlHits) { $htmlHits | Select-Object -First 80 | ForEach-Object { "[Line $($_.LineNumber)]" $_.Context "" } } else { Write-Output '' } Show-RecentKernelPower Show-Section 'Interpretation' Write-Output 'If XML/HTML repeatedly shows "Idle Timeout", that supports the event-log finding that Modern Standby is being entered by idle logic.' Write-Output 'If you see "Session Lock" or "Session Unlock" around the same times, the standby transition may be tied to session state changes rather than a classic power-plan timer.' Write-Output 'If you see "Lid", that is lid-driven, not idle-driven.' Write-Output 'If you see "Human Presence" or "Lock on Leave", presence sensing is involved.' Write-Output 'If you see mostly "Screen Off" followed by "Sleep", the device is following the Modern Standby screen-off-to-sleep path.' Write-Output 'If the report is sparse or unhelpful, keep the fresh XML and HTML paths above and inspect those exact files manually.' Show-Section 'Saved output files' Write-Output "Transcript log : $LogPath" Write-Output "SleepStudy XML : $xmlPath" Write-Output "SleepStudy HTML: $htmlPath" try { Stop-Transcript | Out-Null } catch { }